分佈式環境下創建唯一ID

在我目前的系統中,有多個系統會嘗試寫入數據庫。要求庫中表的第一個字段是全局唯一的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位唯一值的方式來實現,但是到現在沒想到很好的組合,找了幾個都有碰撞。待繼續研究處理。

兩篇很好的參考文章:

http://blog.ddup.us/?p=302

http://www.cnblogs.com/heyuquan/p/3261250.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章