探討分佈式ID生成系統

這裏的博客版本都不會被更新維護。查看最新的版本請移步: http://neojos.com

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

UUID被用來標識URN(Uniform Resource Names),對於Transaction ID以及其餘須要惟一標誌的場景均可以使用它。java

UUID是空間和時間上的惟一標識,它長度固定,內部中包含時間信息。若是服務器時間存在不一樣步的狀況,UUID可能會出現重複。node

UUID構成

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

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

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

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

由於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生成系統
相關文章
相關標籤/搜索