在我目前的系統中,有多個系統會嘗試寫入數據庫。要求庫中表的第一個字段是全局唯一的ID(跨表)。
爲了實現該要求,想了如下的辦法:
1. 自行設計唯一規則;
2. 利用數據庫自增;
3. 利用外部統一的ID生成器。
1先不談。後面詳解。
2. 數據庫自增在跨表時就無法用了,但是可以單獨創建一張表來計數,每次去取LAST_INSERT值。利用了MySQL的replace info。當然也可以自己實現存儲過程,會比較複雜。
CREATE TABLE `TicketsTable` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`stub` char(1) NOT NULL default '',
PRIMARY KEY (`id`),
UNIQUE KEY `stub` (`stub`)
)
向裏邊插入一條記錄後大致是這樣:
+-------------------+------+
| id | stub |
+-------------------+------+
| 72157623227190423 | a |
+-------------------+------+
當需要一個64Bits ID的時候,執行如下SQL 語句:
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
存儲該表的Server如果掛掉,計數就不可用了。所以爲了防止這個Ticket Server單點故障,可以設置兩個Ticket Server實例。其中一個產生奇數ID,另一個產生偶數ID。
TicketServer1:
auto-increment-increment = 2
auto-increment-offset = 1
TicketServer2:
auto-increment-increment = 2
auto-increment-offset = 2
應用交替請求兩個Server,這樣不僅壓力減小一半,故障風險也降低一半。不過這裏也有個問題,就是當一臺機器故障時,另一臺正常機器產生的ID將會領先故障機器一截,可能會造成不再是時間上有序的ID。
Flickr就是使用這種方法,按照他們的說法,這並不影響他們的應用。
3. 這個外部計數器,可以有很多種實現方式:
a.MongoDB
MongoDB中每一條記錄都有一個’id’字段用來唯一標示本記錄。如果用戶插入數據時沒有顯示提供’id’字段,那麼系統會自動生成一個。ObjectID一共12Bytes,設計的時候充分考慮了分佈式環境下使用的情況,所以能保證在一個分佈式MongoDB集羣中唯一。ObjectID格式如下:
0 4 7 9 12
+--------+------+----+------+
|time |pc |pid |inc |
+--------+------+----+------+
前四個字節是Unix Timestamp。
接着三個字節是當前機器“hostname/mac地址/虛擬編號”其中之一的MD5結果的前3個字節。
接着兩個字節是當前進程的PID。
最後三個字節是累加計數器或是一個隨機數(只有當不支持累加計數器時才用隨機數)。
最後生成的仍然是一個用16進製表示的串,如47cc67093475061e3d95369d。這裏MongoDB的ObjectID相對UUID有個很大的優點就是ObjectID是時間上有序的。另外還有ObjectID本身也包含了很多其它有用的信息,通過直接解碼ObjectID即可直接獲得這些信息。
b. Snowflake
Snowflake是twitter開源的一款獨立的適用於分佈式環境的ID生成服務器。生成的ID是64Bits,同時滿足高性能(>10K ids/s),低延遲(<2ms)和高可用。與MongoDB ObjectID類似這裏生成的ID也是時間上有序的。編碼方式也和ObjectID類似,如下:
0 41 51 64
+-----------+------+------+
|time |pc |inc |
+-----------+------+------+
前41bits是以微秒爲單位的timestamp。
接着10bits是事先配置好的機器ID。
最後12bits是累加計數器。
在我的項目裏,上面兩種都不太方便。只能自定義規則。
考慮到MySQL的bigint型最大是64bit長,所以,規則也只能定到64位。
這裏很自然就想到了GUID和UUID,但是長度太長了,128bit。必須得壓縮。可以用base64,但是MurmurHash效率更高。
MurmurHash可以產生32位、64位或128位的定長hash,理論上只要key不同,碰撞機率非常小。
結果,在我用GUID做key去hash,發現32位碰撞的很厲害,64位100萬次測試沒有碰撞,128位就更不用說了。
所以,如果只是要64位唯一ID, GUID+Murmurhash就OK了。
但是,這裏已然有個問題,hash後的ID不具有遞增性,在數據批量處理時會很不方便。所以,在考慮用前32位時間戳,後32位唯一值的方式來實現,但是到現在沒想到很好的組合,找了幾個都有碰撞。待繼續研究處理。
兩篇很好的參考文章: