分布式環境下的id生成方法


前幾天研究數據庫分表分庫的問題,其中有一個關鍵的地方就是生成唯一鍵的問題,假如數據表有1億條數據,而且還在不斷的增加,這里我們就需要考慮到分表分庫,假設我們采用Hash或者是用戶取模求余的方法將這個表拆分成10個表,每個表的結構相同,其中有一個主鍵id,那么10個表中的id需要唯一不同,在單表的時候,使用數據表自增長是沒有問題的。當分成10個表后,就無法用到數據庫自增長了。

當到這里的時候突然發現oracle數據庫的序列真是好東西,在剛剛接觸的時候還很郁悶這種設計真是沒有mysql獲sqlserver中的方便

目前做唯一id的做法基本有三種

1.使用uuid來實現,快速不重復,只是生成的id沒有規則

2.使用外部的id分發中心來實現,生存的id簡短有規則,缺點是依賴於單點

3.在數據庫中做一個計數表來做,有點類是於oracle中的序列

以下內容大部分來自於網絡:

UUID

UUID的目的,是讓分散式系統中的所有元素,都能有唯一的辨識信息,而不需要通過中央控制端來做辨識信息的指定。如此一來,每個人都可以創建不與其它人沖突的UUID。在這樣的情況下,就不需考慮數據庫創建時的名稱重復問題。

一組UUID,是由一串16位組(亦稱128位)的16進位數字所構成,是故UUID理論上的總數為216 x 8=2128,約等於3.4 x 1038。也就是說若每納秒產生1兆個UUID,要花100億年才會將所有UUID用完。

UUID的標准型式包含32個16進位數字,以連字號分為五段,形式為8-4-4-4-12的36個字符。示例:

550e8400-e29b-41d4-a716-446655440000

 

twitter的Snowflake(id分發中心)

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是累加計數器。

MongoDB ObjectID(類似UUID的方式)

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即可直接獲得這些信息。

Ticket Server(數據庫生存方式)

這個是Flickr在遇到生成全局ID問題時采用的辦法。利用了數據庫中auto_increment的特性和MySQL特有的REPLACE INFO命令,專門一個數據庫實例用來產生ID。大致的過程是這樣的:

  • 首先建立一個表,比如用來產生64bitsID的,叫做’Ticket64′
CREATE TABLE `Tickets64` (
  `id` bigint(20) unsigned NOT NULL auto_increment,
  `stub` char(1) NOT NULL default '',
  PRIMARY KEY  (`id`),
  UNIQUE KEY `stub` (`stub`)
) ENGINE=MyISAM
  • 向里邊插入一條記錄后大致是這樣:
+-------------------+------+
| id                | stub |
+-------------------+------+
| 72157623227190423 |    a |
+-------------------+------+
  • 當需要一個64Bits ID的時候,執行如下SQL 語句:
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();

另外為了防止這個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的說法,這並不影響他們的應用。

Instagram采用的方式(UUID方式)

Instagram要將其中存儲的圖片分片到多個PostgreSQL中,其中生成ID的方案和MongoDB ObjectID類似。整個ID的長度為64Bits,設定為這個長度是為了優化在redis中的存儲。ID的編碼格式如下:

  • 41bits以微秒為單位的timestamp,時間起點從2011-01-01開始。
  • 13bits表示進行邏輯分片的Shard ID。
  • 10bits表示一個累加計數器。

ID的生成邏輯用PL/PGSQL語言寫到PostgreSQL數據庫中,當每次插入數據時由數據庫自動計算生成。


免責聲明!

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



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