热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

这篇文章主要介绍了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 comparator = new Comparator() 
{ 
  @Override 
  public int compare(DBObject o1, DBObject o2) 
  { 
    return ((ObjectId)o1.get("_id")).compareTo((ObjectId)o2.get("_id")); 
  } 
}; 
PriorityQueue queue = new PriorityQueue(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 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 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  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 DBObject 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 _id field 
  * @return the modified object o 
  */ 
 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的误区及引起的一系列问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


推荐阅读
  • Allegro总结:1.防焊层(SolderMask):又称绿油层,PCB非布线层,用于制成丝网印板,将不需要焊接的地方涂上防焊剂.在防焊层上预留的焊盘大小要比实际的焊盘大一些,其差值一般 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 本文介绍了如何使用jQuery和AJAX来实现动态更新两个div的方法。通过调用PHP文件并返回JSON字符串,可以将不同的文本分别插入到两个div中,从而实现页面的动态更新。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • Node.js学习笔记(一)package.json及cnpm
    本文介绍了Node.js中包的概念,以及如何使用包来统一管理具有相互依赖关系的模块。同时还介绍了NPM(Node Package Manager)的基本介绍和使用方法,以及如何通过NPM下载第三方模块。 ... [详细]
  • 本文介绍了Java后台Jsonp处理方法及其应用场景。首先解释了Jsonp是一个非官方的协议,它允许在服务器端通过Script tags返回至客户端,并通过javascript callback的形式实现跨域访问。然后介绍了JSON系统开发方法,它是一种面向数据结构的分析和设计方法,以活动为中心,将一连串的活动顺序组合成一个完整的工作进程。接着给出了一个客户端示例代码,使用了jQuery的ajax方法请求一个Jsonp数据。 ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 本文总结了初学者在使用dubbo设计架构过程中遇到的问题,并提供了相应的解决方法。问题包括传输字节流限制、分布式事务、序列化、多点部署、zk端口冲突、服务失败请求3次机制以及启动时检查。通过解决这些问题,初学者能够更好地理解和应用dubbo设计架构。 ... [详细]
  • 本文讨论了在使用Git进行版本控制时,如何提供类似CVS中自动增加版本号的功能。作者介绍了Git中的其他版本表示方式,如git describe命令,并提供了使用这些表示方式来确定文件更新情况的示例。此外,文章还介绍了启用$Id:$功能的方法,并讨论了一些开发者在使用Git时的需求和使用场景。 ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • 像跟踪分布式服务调用那样跟踪Go函数调用链 | Gopher Daily (2020.12.07) ʕ◔ϖ◔ʔ
    每日一谚:“Acacheisjustamemoryleakyouhaven’tmetyet.”—Mr.RogersGo技术专栏“改善Go语⾔编程质量的50个有效实践” ... [详细]
author-avatar
xo麦先生
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有