首先通過終端命令行,向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