分布式系統唯一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。

 

如果想把時間序放在前面,可以生成后改變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)不依賴於數據庫,靈活方便,且性能優於數據庫。

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

缺點:

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

自增ID用Mysql,每個應用每次取100個ID緩存在內存。Mysql對應的行數值加100。類似TDDL 方案。

總體上保持遞增。

嚴格遞增,可以用redis做。


免責聲明!

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



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