文档章节

MongoDB中ObjectId的误区,以及引起的一系列问题

javahongxi
 javahongxi
发布于 2017/08/25 19:14
字数 1822
阅读 7
收藏 0

http://blog.csdn.net/xiamizy/article/details/41521025

近期对两个应用进行改造,在上线过程中出现一系列问题(其中一部分是由于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时,会发现前几位数字很少发现变化的现实,因为使用的是当前时间,很多用户担心要对服务器进行时间同步,其实这个时间戳的真实值并 不重要,只要其总不停增加就好。
  1. ObjectId("53102b43bf1044ed8b0ba36b").getTimestamp();  
  2. ISODate("2014-02-28T06:22:59Z");  

 

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查询语句
[java]  view plain  copy
 
  1. Query query = new Query();  
  2. if (id != null)  
  3. {  
  4.     query.addCriteria(Criteria.where("_id").gt(id));  
  5. }  
  6. query.with(new Sort(Sort.Direction.ASC, "_id"));  
 
2.java.util.PriorityQueue
[java]  view plain  copy
 
  1. Comparator<DBObject> comparator = new Comparator<DBObject>()  
  2. {  
  3.     @Override   
  4.     public int compare(DBObject o1, DBObject o2)  
  5.     {  
  6.         return ((ObjectId)o1.get("_id")).compareTo((ObjectId)o2.get("_id"));  
  7.     }  
  8. };  
  9. 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。那么是否是服务端设置的呢?
大家来看下插入操作的代码:
实现类
[java]  view plain  copy
 
  1. public WriteResult insert(List<DBObject> list, com.mongodb.WriteConcern concern, DBEncoder encoder ){  
  2.   
  3.   
  4.           if (concern == null) {  
  5.               throw new IllegalArgumentException("Write concern can not be null");  
  6.           }  
  7.   
  8.   
  9.           return insert(list, true, concern, encoder);  
  10.       }  

可以看到需要添加,默认都为添加
[java]  view plain  copy
 
  1. protected WriteResult insert(List<DBObject> list, boolean shouldApply , com.mongodb.WriteConcern concern, DBEncoder encoder ){  
  2.   
  3.             if (encoder == null)  
  4.                 encoder = DefaultDBEncoder.FACTORY.create();  
  5.   
  6.             if ( willTrace() ) {  
  7.                 for (DBObject o : list) {  
  8.                     trace( "save:  " + _fullNameSpace + " " + JSON.serialize( o ) );  
  9.                 }  
  10.             }  
  11.   
  12.             if ( shouldApply ){  
  13.                 for (DBObject o : list) {  
  14.                     apply(o);  
  15.                     _checkObject(o, falsefalse);  
  16.                     Object id = o.get("_id");  
  17.                     if (id instanceof ObjectId) {  
  18.                         ((ObjectId) id).notNew();  
  19.                     }  
  20.                 }  
  21.             }  
  22.   
  23.             WriteResult last = null;  
  24.   
  25.             int cur = 0;  
  26.             int maxsize = _mongo.getMaxBsonObjectSize();  
  27.             while ( cur < list.size() ) {  
  28.   
  29.                OutMessage om = OutMessage.insert( this , encoder, concern );  
  30.   
  31.                for ( ; cur < list.size(); cur++ ){  
  32.                     DBObject o = list.get(cur);  
  33.                     om.putObject( o );  
  34.   
  35.                     // limit for batch insert is 4 x maxbson on server, use 2 x to be safe  
  36.                     if ( om.size() > 2 * maxsize ){  
  37.                         cur++;  
  38.                         break;  
  39.                     }  
  40.                 }  
  41.   
  42.                 last = _connector.say( _db , om , concern );  
  43.             }  
  44.   
  45.             return last;  
  46.         }  
自动添加ObjectId的操作
[java]  view plain  copy
 
  1. /** 
  2.    * calls {@link DBCollection#apply(com.mongodb.DBObject, boolean)} with ensureID=true 
  3.    * @param o <code>DBObject</code> to which to add fields 
  4.    * @return the modified parameter object 
  5.    */  
  6.   public Object apply( DBObject o ){  
  7.       return apply( o , true );  
  8.   }  
  9.   
  10.   /** 
  11.    * calls {@link DBCollection#doapply(com.mongodb.DBObject)}, optionally adding an automatic _id field 
  12.    * @param jo object to add fields to 
  13.    * @param ensureID whether to add an <code>_id</code> field 
  14.    * @return the modified object <code>o</code> 
  15.    */  
  16.   public Object apply( DBObject jo , boolean ensureID ){  
  17.   
  18.       Object id = jo.get( "_id" );  
  19.       if ( ensureID && id == null ){  
  20.           id = ObjectId.get();  
  21.           jo.put( "_id" , id );  
  22.       }  
  23.   
  24.       doapply( jo );  
  25.   
  26.       return id;  
  27.   }  
可以看到,mongoDB的驱动包中是会自动添加ObjectId的。
save的方法
[java]  view plain  copy
 
  1. public WriteResult save( DBObject jo, WriteConcern concern ){  
  2.         if ( checkReadOnly( true ) )  
  3.             return null;  
  4.   
  5.         _checkObject( jo , false , false );  
  6.   
  7.         Object id = jo.get( "_id" );  
  8.   
  9.         if ( id == null || ( id instanceof ObjectId && ((ObjectId)id).isNew() ) ){  
  10.             if ( id != null && id instanceof ObjectId )  
  11.                 ((ObjectId)id).notNew();  
  12.             if ( concern == null )  
  13.                 return insert( jo );  
  14.             else  
  15.                 return insert( jo, concern );  
  16.         }  
  17.   
  18.         DBObject q = new BasicDBObject();  
  19.         q.put( "_id" , id );  
  20.         if ( concern == null )  
  21.             return update( q , jo , true , false );  
  22.         else  
  23.             return update( q , jo , true , false , concern );  
  24.   
  25.     }  
 
综上所述,默认情况下ObjectId是由客户端生成的,并 不是不设置就由服务端生成的。
 

误区 四 、findAndModify是否真的可以获取到自增变量?

[java]  view plain  copy
 
  1. DBObject update = new BasicDBObject("$inc"new BasicDBObject("counter"1));  
  2.         DBObject query = new BasicDBObject("_id", key);  
  3.         DBObject result = getMongoTemplate().getCollection(collectionName).findAndModify(query, update);  
  4.         if (result == null)  
  5.         {  
  6.             DBObject doc = new BasicDBObject();  
  7.             doc.put("counter", 1L);  
  8.             doc.put("_id", key);  
  9.             // insert(collectionName, doc);  
  10.             getMongoTemplate().save(doc, collectionName);  
  11.             return 1L;  
  12.         }  
  13.         return (Long) result.get("counter");  

获取自增变量会使用这种方法编写,但是,我们执行完成后会发现。
findAndModify操作,是先执行了find,再执行了modify,所以当result为null时,应该新增并返回0

© 著作权归作者所有

javahongxi
粉丝 168
博文 259
码字总数 779422
作品 0
朝阳
程序员
私信 提问
MongoDB中ObjectId的误区,以及引起的一系列问题

近期对两个应用进行改造,在上线过程中出现一系列问题(其中一部分是由于ObjectId误区导致的) 先来了解下ObjectId: TimeStamp 前 4位是一个unix的时间戳,是一个int类别,我们将上面的例子...

齐国隆冬强
2014/11/27
100
0
渐入佳境的MongoDB的查询之基础篇

【IT168 专稿】引言   MongoDB是NoSQL数据库新产品中的重要成员,已经获得众多机构的关注。NoSQL数据管理方法的优点一直在软件社区引起激烈讨论,正如MongoDB一样,值得花费一些时间探索解...

作者:孙耀堃 译
2012/05/14
0
0
大偏移量下Redis和MongoDB性能比较

【IT168 技术】题目其实并不太准确,因为数据库并不会提供分页、排名等功能,提供的只是数据的存取,分页排名这些都是我们基于数据库的实用案例而已。然而无论是Redis还是MongoDB,通常都有一...

作者:NoSQLfan
2012/09/28
0
0
mongodb怎么批量把集合内文档中的一个属性值赋给另个一属性

mongodb怎么批量把集合内文档中的一个属性值赋给另个一属性,如下为集合logs中的内容,怎么批量把每个文档中category的值赋值给data。 { "_id" : ObjectId("543747648cb646e609000001"), "dat......

crazyjingling
2014/11/20
976
3
初识MongoDB-用法和用途

用意:记录一下自己学习MongoDB的一些体会. 适合范围, 安装和用法. 一. MongoDB相关 JavaScript统一天下 JavaScript正在快节奏地统一整个web开发流程. JvaScript是WEB前端开发的唯一依靠, 依赖...

清风漫步
2014/02/21
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

java通过ServerSocket与Socket实现通信

首先说一下ServerSocket与Socket. 1.ServerSocket ServerSocket是用来监听客户端Socket连接的类,如果没有连接会一直处于等待状态. ServetSocket有三个构造方法: (1) ServerSocket(int port);...

Blueeeeeee
今天
6
0
用 Sphinx 搭建博客时,如何自定义插件?

之前有不少同学看过我的个人博客(http://python-online.cn),也根据我写的教程完成了自己个人站点的搭建。 点此:使用 Python 30分钟 教你快速搭建一个博客 为防有的同学不清楚 Sphinx ,这...

王炳明
昨天
5
0
黑客之道-40本书籍助你快速入门黑客技术免费下载

场景 黑客是一个中文词语,皆源自英文hacker,随着灰鸽子的出现,灰鸽子成为了很多假借黑客名义控制他人电脑的黑客技术,于是出现了“骇客”与"黑客"分家。2012年电影频道节目中心出品的电影...

badaoliumang
昨天
15
0
很遗憾,没有一篇文章能讲清楚线程的生命周期!

(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本。 简介 大家都知道线程是有生命周期,但是彤哥可以认真负责地告诉你网上几乎没有一篇文章讲得是完全正确的。 ...

彤哥读源码
昨天
15
0
jquery--DOM操作基础

本文转载于:专业的前端网站➭jquery--DOM操作基础 元素的访问 元素属性操作 获取:attr(name);$("#my").attr("src"); 设置:attr(name,value);$("#myImg").attr("src","images/1.jpg"); ......

前端老手
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部