探討分散式ID生成系統

neojos發表於2019-01-19

這裡的部落格版本都不會被更新維護。檢視最新的版本請移步:http://neojos.com

全稱Universally Unique IdentifierUUID佔128bit,也就是16個英文字元的長度(16byte),需要強調的是,它的生成無需中心處理程式。

UUID被用來標識URN(Uniform Resource Names),對於Transaction ID以及其他需要唯一標誌的場景都可以使用它。

UUID是空間和時間上的唯一標識,它長度固定,內部中包含時間資訊。如果伺服器時間存在不同步的情況,UUID可能會出現重複。

UUID構成

基本格式,由6部分組成:

time-low - time-mide - time-high-and-version - clock-seq-and-reserved & clock-seq-low - node

一個URN示例:f81d4fae-7dec-11d0-a765-00a0c91e6bf6

因為UUID佔128bit,16進位制數佔4bit,所以轉換成16進位制0-f的字串總共有32位。組成的各個部分具體由幾位16進製表示,請查閱 Namespace Registration Template

因為UUID太長且無序,導致其不適合做MySQL的主鍵索引。而且MySQL自帶的auto-increment功能,選擇bigint的話也只佔用64bit

All indexes other than the clustered index are known as secondary indexes. In InnoDB, each record in a secondary index contains the primary key columns for the row, as well as the columns specified for the secondary index. InnoDB uses this primary key value to search for the row in the clustered index.

If the primary key is long, the secondary indexes use more space, so it is advantageous to have a short primary key.

MongoDB`s ObjectId

ObjectId由佔4-byte的時間戳、3-byte的機器標識、2-byte的程式ID以及3-byte的計陣列成,總共還是佔用96bit

這些ID組成包括時間、機器標識、隨機數,在UUID生成時還使用到MAC地址。這些引數中時間是關鍵,保證叢集伺服器的時鐘準確非常重要。

Twitter Snowflake

Twitter Snowflake生成的ID佔64bit,跟bigint大小一致。由41 bit毫秒精度的時間戳、10bit的機器ID以及12 bit的序列號組成(計數每4096就重新開始一輪),剩下的1 bit奉獻給未來。

作者修改了它的原始設定,將剩下的1 bit給了時間戳。使用機器MAC地址的HASH值作為當前機器的ID

服務全域性儲存最近一次生成ID的時間戳lastTimestamp,作為生成新ID的判斷依據,避免時間回溯。詳細程式碼請參照[1]

// Block and wait till next millisecond
private long waitNextMillis(long currentTimestamp) {
    while (currentTimestamp == lastTimestamp) {
        currentTimestamp = timestamp();
    }
    return currentTimestamp;
}

同時將sequence也宣告為全域性變數,每間隔4096次就重新開始計數。主要用於應對:當時間戳相同時保證生成的ID是不同的。

if (currentTimestamp == lastTimestamp) {
    sequence = (sequence + 1) & maxSequence;
    if(sequence == 0) {
        // Sequence Exhausted, wait till next millisecond.
        currentTimestamp = waitNextMillis(currentTimestamp);
    }
} else {
    // reset sequence to start with zero for the next millisecond
    sequence = 0;
}

Database Ticket Servers

該方式通過中心的DB服務來生成唯一自增ID,但DB服務的寫操作會成為系統的瓶頸。如果後臺是單個DB服務的話,存在單點問題。

參考Flickr的方法,後臺使用兩個DB來生成ID,其中auto-increment一個按照奇數步長增長,另一個按照偶數步長增長。MySQL內部使用REPLACE來實現,通過一條衝突的記錄,來持續生成自增的主鍵ID

REPLACE makes sense only if a table has a PRIMARY KEY or UNIQUE index. Otherwise, it becomes equivalent to INSERT, because there is no index to be used to determine whether a new row duplicates another.

結合Twitter SnowflakeID做如下調整:41-bit的毫秒時間戳、13-bit的資料邏輯分割槽以及10-bit的自增序列。自增序列對1024取餘,每個分割槽每毫秒內能生成1024個自增ID

Flickr中各個資料表按照不同的步長增長,當需要分表的時候就會存在巨複雜的資料遷移問題。為了解決這個問題,便引入了邏輯分割槽Shard ID。通過邏輯上的Shard,將資料分散在不同的資料表中。這樣後續的分庫分表都可以通過操作邏輯上Shard來實現,將DB從具體的實現中解脫出來。

關於獲取MySQL自增ID,程式碼無法批量獲取插入的全部自增ID列表,MySQL只會返回第一條記錄的自增ID。因為自增ID是連續的,所以可以通過計算的方式來計算出ID列表。

If you insert multiple rows using a single INSERT statement, LAST_INSERT_ID() returns the value generated for the first inserted row only. The reason for this is to make it possible to reproduce easily the same INSERT statement against some other server.

關於Shard可以檢視本地快取BigCache,很有參考意義(我覺得)。

總結

文中介紹了ID的兩種生成方式,核心的區別在於:整個系統的ID是否支援單調遞增。Twitter Snowflake以及UUID可以保證生成的資料唯一,但多臺伺服器的話,無法保證生成的資料有序。而Ticket Servers通過結合MySQLauto-increment解決了這個問題。


參考文章:

  1. Generating unique IDs in a distributed environment at high scale
  2. A Universally Unique IDentifier (UUID) URN Namespace
  3. Clustered and Secondary Indexes
  4. Sharding & IDs at Instagram
  5. Ticket Server: Distributed Unique Primary Keys on the Cheap
  6. MySQL批量插入返回自增ID的問題
  7. Leaf——美團點評分散式ID生成系統

相關文章