天天看點

分布式系統唯一ID生成方案

分布式系統唯一ID生成方案彙總

資料庫自增主鍵

最常見的方式。利用資料庫,全資料庫唯一。

優點:

1)簡單,代碼友善,性能可以接受。

2)數字ID天然排序,對分頁或者需要排序的結果很有幫助。

缺點:

1)不同資料庫文法和實作不同,資料庫遷移的時候或多資料庫版本支援的時候需要處理。

2)在單個資料庫或讀寫分離或一主多從的情況下,隻有一個主庫可以生成。有單點故障的風險。

3)在性能達不到要求的情況下,比較難于擴充。

4)如果遇見多個系統需要合并或者涉及到資料遷移會相當痛苦。

5)分表分庫的時候會有麻煩。

優化方案:

1)針對主庫單點,如果有多個Master庫,則每個Master庫設定的起始數字不一樣,步長一樣,可以是Master的個數。比如:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。這樣就可以有效生成叢集中的唯一ID,也可以大大降低ID生成資料庫操作的負載。

這種方式可保證id不重複。但導緻id不是絕對遞增,而是整體趨勢上遞增;其次是寫入的壓力仍然很大,mysql容易成為性能瓶頸。

資料庫批量生成id(TDDL采用)

優點:效率高;降低資料庫壓力

缺點:需考慮安全性問題,防止取到重複id;如果業務需求是每次隻生成一個id,性能有問題

備注:利用資料庫,初始化一行資料,初始值為1,取10個id,就給該值加10,調用端取傳回id值的前10個數值。以上即為批量生成id思路。

UUID

常見的方式。可以利用資料庫也可以利用程式生成,一般來說全球唯一。

1)簡單,代碼友善。

2)生成ID性能非常好,基本不會有性能問題。

3)全球唯一,在遇見資料遷移,系統資料合并,或者資料庫變更等情況下,可以從容應對。

1)沒有排序,無法保證趨勢遞增。

2)UUID往往是使用字元串存儲,查詢的效率比較低。

3)存儲空間比較大,如果是海量資料庫,就需要考慮存儲量的問題。

4)傳輸資料量大

5)不可讀。

UUID變種

1)為了解決UUID不可讀,可以使用UUID to Int64的方法。即

/// <summary>
/// 根據GUID擷取唯一數字序列
/// </summary>
public static long GuidToInt64()
{
    byte[] bytes = Guid.NewGuid().ToByteArray();
    return BitConverter.ToInt64(bytes, 0);
}      

2)為了解決UUID無序的問題,NHibernate在其主鍵生成方式中提供了Comb算法(combined guid/timestamp)。保留GUID的10個位元組,用另6個位元組表示GUID生成的時間(DateTime)。

/// <summary> 
/// Generate a new <see cref="Guid"/> using the comb algorithm. 
/// </summary> 
private Guid GenerateComb()
{
    byte[] guidArray = Guid.NewGuid().ToByteArray();
 
    DateTime baseDate = new DateTime(1900, 1, 1);
    DateTime now = DateTime.Now;
 
    // Get the days and milliseconds which will be used to build    
    //the byte string    
    TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks);
    TimeSpan msecs = now.TimeOfDay;
 
    // Convert to a byte array        
    // Note that SQL Server is accurate to 1/300th of a    
    // millisecond so we divide by 3.333333    
    byte[] daysArray = BitConverter.GetBytes(days.Days);
    byte[] msecsArray = BitConverter.GetBytes((long)
      (msecs.TotalMilliseconds / 3.333333));
 
    // Reverse the bytes to match SQL Servers ordering    
    Array.Reverse(daysArray);
    Array.Reverse(msecsArray);
 
    // Copy the bytes into the guid    
    Array.Copy(daysArray, daysArray.Length - 2, guidArray,
      guidArray.Length - 6, 2);
    Array.Copy(msecsArray, msecsArray.Length - 4, guidArray,
      guidArray.Length - 4, 4);
 
    return new Guid(guidArray);
}      

用上面的算法測試一下,得到如下的結果:作為比較,前面3個是使用COMB算法得出的結果,最後12個字元串是時間序(統一毫秒生成的3個UUID),過段時間如果再次生成,則12個字元串會比圖示的要大。後面3個是直接生成的GUID。

分布式系統唯一ID生成方案

如果想把時間序放在前面,可以生成後改變12個字元串的位置,也可以修改算法類的最後兩個Array.Copy。

Redis生成ID

原子操作 INCR和INCRBY

當使用資料庫來生成ID性能不夠要求的時候,我們可以嘗試使用Redis來生成ID。這主要依賴于Redis是單線程的,是以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY來實作。

可以使用Redis叢集來擷取更高的吞吐量。假如一個叢集中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然後步長都是5。各個Redis生成的ID為:

A:1,6,11,16,21

B:2,7,12,17,22

C:3,8,13,18,23

D:4,9,14,19,24

E:5,10,15,20,25

這個,随便負載到哪個機确定好,未來很難做修改。但是3-5台伺服器基本能夠滿足器上,都可以獲得不同的ID。但是步長和初始值一定需要事先需要了。使用Redis叢集也可以方式單點故障的問題。

另外,比較适合使用Redis來生成每天從0開始的流水号。比如訂單号=日期+當日自增長号。可以每天在Redis中生成一個Key,使用INCR進行累加。

1)不依賴于資料庫,靈活友善,且性能優于資料庫。

1)如果系統中沒有Redis,還需要引入新的元件,增加系統複雜度。

2)需要編碼和配置的工作量比較大。

EVAL,EVALSHA兩個指令(LUA)

 GitHub 位址:​​https://github.com/hengyunabc/redis-id-generator​​

依賴redis的EVAL,EVALSHA兩個指令,利用redis的lua腳本執行功能,在每個節點上通過lua腳本生成唯一ID。 生成的ID是64位的:

使用41 bit來存放時間,精确到毫秒,可以使用41年。

使用12 bit來存放邏輯分片ID,最大分片ID是4095

使用10 bit來存放自增長ID,意味着每個節點,每毫秒最多可以生成1024個ID

Redis提供了TIME指令,可以取得redis伺服器上的秒數和微秒數。因些lua腳本傳回的是一個四元組。

second, microSecond, partition, seq      

用戶端要自己處理,生成最終ID。

((second * 1000 + microSecond / 1000) << (12 + 10)) + (shardId << 10) + seq;      

在redis-id-generator-java目錄下,有example和benchmark代碼,提供了 Java用戶端生成模式,其它語言隻要支援redis evalsha指令就可以了。

ZK生成唯一ID

zookeeper主要通過其znode資料版本來生成序列号,可以生成32位和64位的資料版本号,用戶端可以使用這個版本号來作為唯一的序列号。

很少會使用zookeeper來生成唯一ID。主要是由于需要依賴zookeeper,并且是多步調用API,如果在競争較大的情況下,需要考慮使用分布式鎖。是以,性能在高并發的分布式環境下,也不甚理想。

Stat stat = zkClient.writeData("/seq", new byte[0], -1);
nt versionAsSeq = stat.getVersion();
System.out.println(taskName + " obtain seq=" +versionAsSeq );      

往指定節點寫資料,每次寫成功,拿到的版本号用來做唯一ID,性能不大好。

snowflake

分布式ID生成器--SnowFlake

公司用

時間戳+機器ID+自增ID