MongoDB中ObjectId的誤區,以及引起的一系列問題

來源:互聯網
上載者:User

標籤: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查詢語句
Query query = new Query();if (id != null){query.addCriteria(Criteria.where("_id").gt(id));}query.with(new Sort(Sort.Direction.ASC, "_id"));


2.java.util.PriorityQueue
Comparator<DBObject> comparator = new Comparator<DBObject>(){@Overridepublic 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的誤區,以及引起的一系列問題

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.