这里有新鲜出炉的 MongoDB 手册, 程序狗速度看过来!
MongoDB 分布式文件存储的数据库
MongoDB 是一个基于分布式文件存储的数据库由 C++ 语言编写旨在为 web 应用提供可扩展的高性能数据存储解决方案
这篇文章主要介绍了 MongoDB 中 ObjectId 的误区及引起的一系列问题, 非常不错, 具有参考借鉴价值, 需要的朋友可以参考下
近期对两个应用进行改造, 在上线过程中出现一系列问题(其中一部分是由于 ObjectId 误区导致的)
先来了解下 ObjectId:
TimeStamp
前 4 位是一个 unix 的时间戳, 是一个 int 类别, 我们将上面的例子中的 objectid 的前 4 位进行提取 4df2dcec, 然后再将他们安装十六进制 专为十进制: 1307761900, 这个数字就是一个时间戳, 为了让效果更佳明显, 我们将这个时间戳转换成我们习惯的时间格式(精确到秒)
$ date -d '1970-01-01 UTC 1307761900 sec' -u
2011 年 06 月 11 日 星期六 03:11:40 UTC
前 4 个字节其实隐藏了文档创建的时间, 并且时间戳处在于字符的最前面, 这就意味着 ObjectId 大致会按照插入进行排序, 这对于某些方面起到很大作用, 如 作为索引提高搜索效率等等使用时间戳还有一个好处是, 某些客户端驱动可以通过 ObjectId 解析出该记录是何时插入的, 这也解答了我们平时快速连续创 建多个 Objectid 时, 会发现前几位数字很少发现变化的现实, 因为使用的是当前时间, 很多用户担心要对服务器进行时间同步, 其实这个时间戳的真实值并 不重要, 只要其总不停增加就好
Machine
接下来的三个字节, 就是 2cdcd2 , 这三个字节是所在主机的唯一标识符, 一般是机器主机名的散列值, 这样就确保了不同主机生成不同的机器 hash 值, 确保在分布式中不造成冲突, 这也就是在同一台机器生成的 objectid 中间的字符串都是一模一样的原因
pid
上面的 Machine 是为了确保在不同机器产生的 objectid 不冲突, 而 pid 就是为了在同一台机器不同的 mongodb 进程产生了 objectid 不冲突, 接下来的 0936 两位就是产生 objectid 的进程标识符
increment
前面的九个字节是保证了一秒内不同机器不同进程生成 objectid 不冲突, 这后面的三个字节 a8b817, 是一个自动增加的计数器, 用来确保在同一秒内产生的 objectid 也不会发现冲突, 允许 256 的 3 次方等于 16777216 条记录的唯一性
ObjectId 唯一性
大家可能会觉得, 在某种程度上已经可以保证唯一了, 不管在客户端还是在服务端
误区 一 文档顺序和插入顺序一致?
单线程情况
ObjectId 中的 timestampmachinepidinc 都可以保证唯一, 因为在同一台机器, 同一个进程
这里有一个问题, mongodb 的操作时多线程的 abc... 几个线程进行入库操作时, 不能保证哪一条可以在另外一条之前, 所以会是乱序的
多线程多机器或多进程情况
再看下 ObjectId 中 machepid 不能保证唯一那么则数据更加会是乱序的
解决办法:
由于 collection 集合中数据是无序的(包括 capped collection), 那么, 最简单的办法是对 ObjectId 进行排序
可以使用两种方法排序,
1.mongoDB 查询语句
- jQuery query = new Query();
- if (id != null)
- {
- jquery.addCriteria(Criteria.where("_id").gt(id));
- }
- jquery.with(new Sort(Sort.Direction.ASC, "_id"));
- 2.java.util.PriorityQueue
- Comparator<DBObject> comparator = new Comparator<DBObject>()
- {
- @Override
- public int compare(DBObject o1, DBObject o2)
- {
- return ((ObjectId)o1.get("_id")).compareTo((ObjectId)o2.get("_id"));
- }
- };
- PriorityQueue<DBObject> queue = new PriorityQueue<DBObject>(200,comparator);
误区 二 多客户端高并发时, 是否可以保证顺序(sort 之后)?
如果一直保证写入远远大于读出(间隔一秒以上), 这样是永远不会出现乱序的情况
我们来看下样例
现在看到图中, 取出数据两次
第一次
- 4df2dcec aaaa ffff 36a8b813
- 4df2dcec aaaa eeee 36a8b813
- 4df2dcec bbbb 1111 36a8b814
第二次
- 4df2dcec bbbb 1111 36a8b813
- 4df2dcec aaaa ffff 36a8b814
- 4df2dcec aaaa eeee 36a8b814
现在如果取第一次的最大值 (4df2dcec bbbb 1111 36a8b814) 做下次查询的结果, 那么就会漏掉
第二次的三条, 因为 (4df2dcec bbbb 1111 36a8b814) 大于第二次取的所有记录
所以会导致丢数据的情况
解决办法:
由于 ObjectId 的时间戳截止到秒, 而 counter 算子前四位又为机器与进程号
1. 处理一定时间间隔前的记录(一秒以上), 这样即使机器和进程号导致乱序, 间隔前也不会出现乱序情况
2. 单点插入, 原来分布到几个点的插入操作, 现在统一由一个点查询, 保证机器与进程号相同, 使用 counter 算子使记录有序
这里, 我们用到了第一种办法
误区 三 不在 DBObject 设置_id 使用 mongoDB 设置 ObjectId?
mongoDB 插入操作时, new DBBasicObject()时, 大家看到_id 是没有被填值的, 除非手工的设置_id 那么是否是服务端设置的呢?
大家来看下插入操作的代码:
实现类
- public WriteResult insert(List<DBObject> list, com.mongodb.WriteConcern concern, DBEncoder encoder ){
- if (concern == null) {
- throw new IllegalArgumentException("Write concern can not be null");
- }
- return insert(list, true, concern, encoder);
- }
可以看到需要添加, 默认都为添加
- protected WriteResult insert(List<DBObject> list, boolean shouldApply , com.mongodb.WriteConcern concern, DBEncoder encoder ){
- if (encoder == null)
- encoder = DefaultDBEncoder.FACTORY.create();
- if ( willTrace() ) {
- for (DBObject o : list) {
- trace( "save:" + _fullNameSpace + " " + JSON.serialize( o ) );
- }
- }
- if ( shouldApply ){
- for (DBObject o : list) {
- apply(o);
- _checkObject(o, false, false);
- Object id = o.get("_id");
- if (id instanceof ObjectId) {
- ((ObjectId) id).notNew();
- }
- }
- }
- WriteResult last = null;
- int cur = 0;
- int maxsize = _mongo.getMaxBsonObjectSize();
- while ( cur <list.size() ) {
- OutMessage om = OutMessage.insert( this , encoder, concern );
- for ( ; cur < list.size(); cur++ ){
- DBObject o = list.get(cur);
- om.putObject( o );
- // limit for batch insert is 4 x maxbson on server, use 2 x to be safe
- if ( om.size()> 2 * maxsize ){
- cur++;
- break;
- }
- }
- last = _connector.say( _db , om , concern );
- }
- return last;
- }
自动添加 ObjectId 的操作
- /**
- * calls {@link DBCollection#apply(com.mongodb.DBObject, boolean)} with ensureID=true
- * @param o <code>DBObject</code> to which to add fields
- * @return the modified parameter object
- */
- public Object apply( DBObject o ){
- return apply( o , true );
- }
- /**
- * calls {@link DBCollection#doapply(com.mongodb.DBObject)}, optionally adding an automatic _id field
- * @param jo object to add fields to
- * @param ensureID whether to add an <code>_id</code> field
- * @return the modified object <code>o</code>
- */
- public Object apply( DBObject jo , boolean ensureID ){
- Object id = jo.get( "_id" );
- if ( ensureID && id == null ){
- id = ObjectId.get();
- jo.put( "_id" , id );
- }
- doapply( jo );
- return id;
- }
可以看到, mongoDB 的驱动包中是会自动添加 ObjectId 的
save 的方法
- public WriteResult save( DBObject jo, WriteConcern concern ){
- if ( checkReadOnly( true ) )
- return null;
- _checkObject( jo , false , false );
- Object id = jo.get( "_id" );
- if ( id == null || ( id instanceof ObjectId && ((ObjectId)id).isNew() ) ){
- if ( id != null && id instanceof ObjectId )
- ((ObjectId)id).notNew();
- if ( concern == null )
- return insert( jo );
- else
- return insert( jo, concern );
- }
- DBObject q = new BasicDBObject();
- q.put( "_id" , id );
- if ( concern == null )
- return update( q , jo , true , false );
- else
- return update( q , jo , true , false , concern );
- }
综上所述, 默认情况下 ObjectId 是由客户端生成的, 并不是不设置就由服务端生成的
误区 四 findAndModify 是否真的可以获取到自增变量?
- DBObject update = new BasicDBObject("$inc", new BasicDBObject("counter", 1));
- DBObject query = new BasicDBObject("_id", key);
- DBObject result = getMongoTemplate().getCollection(collectionName).findAndModify(query, update);
- if (result == null)
- {
- DBObject doc = new BasicDBObject();
- doc.put("counter", 1L);
- doc.put("_id", key);
- // insert(collectionName, doc);
- getMongoTemplate().save(doc, collectionName);
- return 1L;
- }
- return (Long) result.get("counter");
获取自增变量会使用这种方法编写, 但是, 我们执行完成后会发现
findAndModify 操作, 是先执行了 find, 再执行了 modify, 所以当 result 为 null 时, 应该新增并返回 0
以上所述是小编给大家介绍的 MongoDB 中 ObjectId 的误区及引起的一系列问题, 希望对大家有所帮助, 如果大家有任何疑问请给我留言, 小编会及时回复大家的在此也非常感谢大家对 PHPERZ 网站的支持!
来源: http://www.phperz.com/article/18/0312/359063.html