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中的timestamp、machine、pid、inc都可以保证唯一,因为在同一台机器,同一个进程。 这里有一个问题,mongodb的操作时多线程的。a、b、c...几个线程进行入库操作时,不能保证哪一条可以在另外一条之前,所以会是乱序的。 多线程、多机器或多进程情况 再看下ObjectId中mache、pid不能保证唯一。那么则数据更加会是乱序的。 解决办法: 由于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 bbbb 1111 36a8b813 现在如果取第一次的最大值(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,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 ); 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,false ); else return update( q,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的误区及引起的一系列问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程小技巧网站的支持! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- java – 不能从ActionProvider转换为ShareActionProvider
- java – s.equals(“”)和“”.equals(s)之间有什么区别
- java – Eclipse Google -App -Engine“不会增强”
- 关于Cloneable接口和clone方法
- 如何回到Java中的特定行?
- P3067 [USACO12OPEN]平衡的奶牛群(折半暴搜)
- java – 矢量图形在iText PDF
- Java Map.values()方法:获取Map集合中的所有键值对象
- java webserver-封装request请求协议
- java – 使用AtomicInteger作为可变整数的替代是一种好习惯