MongoDB之ObjectId


一、ObjectId的組成

首先通過終端命令行,向mongodb的collection中插入一條不帶“_id”的記錄。然后,通過查詢剛插入的數據,發現自動生成了一個objectId,4e7020cb7cac81af7136236b。

“4e7020cb7cac81af7136236b”這個24位的字符串,雖然看起來很長,也很難理解,但實際上它是由一組十六進制的字符構成,每個字節兩位的十六進制數字,總共用了12字節的存儲空間。相比MYSQL int類型的4個字節,MongoDB確實多出了很多字節。不過按照現在的存儲設備,多出來的字節應該不會成為什么瓶頸。不過MongoDB的這種設計,體現着空間換時間的思想。

 ObjectId的官方規范

1)     Time

時間戳。將剛才生成的objectid的前4位進行提取“4e7020cb”,然后按照十六進制轉為十進制,變為“1315971275”,這個數字就是一個時間戳。通過時間戳的轉換,就成了易看清的時間格式,

2)    Machine

機器。接下來的三個字節就是“7cac81”,這三個字節是所在主機的唯一標識符,一般是機器主機名的散列值,這樣就確保了不同主機生成不同的機器hash值,確保在分布式中不造成沖突,這也就是在同一台機器生成的objectId中間的字符串都是一模一樣的原因。

3)    PID

進程ID。上面的Machine是為了確保在不同機器產生的objectId不沖突,而pid就是為了在同一台機器不同的mongodb進程產生了objectId不沖突,接下來的“af71”兩位就是產生objectId的進程標識符。

4)    INC

自增計數器。前面的九個字節是保證了一秒內不同機器不同進程生成objectId不沖突,這后面的三個字節“36236b”是一個自動增加的計數器,用來確保在同一秒內產生的objectId也不會發現沖突,允許256的3次方等於16777216條記錄的唯一性。

總的來看,objectId的前4個字節時間戳,記錄了文檔創建的時間;接下來3個字節代表了所在主機的唯一標識符,確定了不同主機間產生不同的objectId;后2個字節的進程id,決定了在同一台機器下,不同mongodb進程產生不同的objectId;最后通過3個字節的自增計數器,確保同一秒內產生objectId的唯一性。ObjectId的這個主鍵生成策略,很好地解決了在分布式環境下高並發情況主鍵唯一性問題,值得學習借鑒。

二、源碼分析

MongoDB可以通過自身的服務來產生objectId,也可以通過客戶端的驅動程序來生成objectId。雖然objectId是輕量級的,但如果全部在服務端生成肯定會花費一點開銷。所以,能從服務器端轉移到客戶端驅動程序完成的,就盡量轉移到客戶端來完成,減少服務器端的開銷。我們來看一下,客戶端的驅動程序是如何來生成objectId的。

1、下載mongodb java driver源碼。 (https://github.com/mongodb/mongo-java-driver/downloads)

2、分析ObjectId.java

驅動源碼的org.bson包下找到ObjectId.java,進行分析。默認構建的objectId代碼如下代碼所示,objectId主要由_time,_machine和_inc組成。

構建objectId 
public class ObjectId implements Comparable<ObjectId> , java.io.Serializable {
final int _time;
    final int _machine;
    final int _inc;
boolean _new;
public ObjectId(){
        _time = (int) (System.currentTimeMillis() / 1000);
        _machine = _genmachine;
        _inc = _nextInc.getAndIncrement();
        _new = true;
}
……
}

1)     _time

直接由System.currentTimeMillis()/1000計算得出的時間戳。

2)    _machine

由機器碼(machinePiece)和進程碼(processPiece)組成,如代碼所示。它這里組成方式是:首先,通過NetworkInterface這個類,獲取機器的所有網絡接口信息,並將得到的字符串取散列值,就得到了機器碼;然后通過RuntimeMXBean.getName()方法獲取pid,再拼裝classloaderid,得到進程碼;最后將機器碼和進程碼進行位或運算得到_machine。不過這里生成的_machine是十進制的,需轉成十六進制。

機器碼和進程碼的生成
private static final int _genmachine;
static {
try {
final int machinePiece;
        {
StringBuilder sb = new StringBuilder();
            Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
            while ( e.hasMoreElements() ){
                NetworkInterface ni = e.nextElement();
                sb.append( ni.toString() );
            }
            machinePiece = sb.toString().hashCode() << 16;
            LOGGER.fine( "machine piece post: " + Integer.toHexString( machinePiece ) );
}
final int processPiece;
        {
            int processId = new java.util.Random().nextInt();
            try {
processId = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().hashCode();
}catch ( Throwable t ){
}
ClassLoader loader = ObjectId.class.getClassLoader();
            int loaderId = loader != null ? System.identityHashCode(loader) : 0;
StringBuilder sb = new StringBuilder();
            sb.append(Integer.toHexString(processId));
            sb.append(Integer.toHexString(loaderId));
            processPiece = sb.toString().hashCode() & 0xFFFF;
            LOGGER.fine( "process piece: " + Integer.toHexString( processPiece ) );
        }
_genmachine = machinePiece | processPiece;
        LOGGER.fine( "machine : " + Integer.toHexString( _genmachine ) );
    }catch ( java.io.IOException ioe ){
        throw new RuntimeException( ioe );
    }
}

3)    _inc

自增數是通過AtomicInteger

更多內容可參考:http://forum.foxera.com/mongodb/category/6/%E6%95%99%E5%AD%A6%E5%8C%BA


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM