各種分佈式全局唯一ID生成算法彙總大全(共12種)

​    各種分佈式全局唯一ID生成算法彙總大全(共12種);設計分佈式微服務系統,面試看這篇就夠了。

 

   在複雜分佈式系統中,往往需要對大量的數據和消息進行唯一標識。在系統中,數據日漸增長,對數據分庫分表後需要有一個唯一ID來標識一條數據或消息,數據庫的自增ID顯然不能滿足需求;此時一個能夠生成全局唯一ID的系統是非常必要的。概括下來,那業務系統對ID號的要求有哪些呢?

1.     全局唯一性:不能出現重複的ID號,保證生成的 ID 全局唯一,這是最基本的要求。

2.     趨勢遞增:有利於保證DB插入、查詢等操作的性能。

3.     單調遞增:保證下一個ID一定大於上一個ID,例如版本號、排序等特殊需求。

4.     信息安全:如果ID是連續的,容易被猜測出url,ID號碼的數量,對業務產生影響。所以在一些應用場景下,會需要ID無規則、不規則。

上述需求對應不同的場景,3和4需求還是互斥的,無法使其在同一個方案滿足。

同時除了對ID號碼自身的要求,業務還對ID號生成系統的可用性要求極高,想象一下,如果ID生成系統癱瘓,整個電商系統都無法完成業務,這就會帶來一場災難。

由此總結出一個ID生成系統應該做到如下幾點:

     1.     平均延遲和TP999延遲都要儘可能低;

     2.     可用性5個9;

     3.     高QPS。

 

劃重點了:如果前9種,你都看過了,就直接跳到第10種看吧。

分段模式與雪花到底有什麼區別?

一個是依賴DB,一個是依賴時間的.

能不能找到一種,既不依賴DB,也不依賴時間的算法呢?答案是,肯定有的,找吧(本文就能找到)!

DB表自增ID真的不能用在分佈式場景嗎?  不要讓前人把你的思維帶偏了!

 

以下,將爲大家講解各種分佈式全局唯一ID生成算法(共12種)。

  1.   基於數據庫自增ID(單個DB)

  2.   基於數據庫自增ID(主從DB結構)

  3. UUID  全球唯一,但生成的是字符串。

  4. Redis

  5. 號段模式

  6. twitter snowflake   Twitter時鐘回撥時直接拋異常。時鐘回撥時會不可用。

  7. 美團leaf: 該篇文章詳細的介紹了snowflake和db號段方案,近期也進行了Leaf開源。

還可以完善的地方:

沒有:連續單調遞增ID生成方案;沒有批量獲取ID號的方法。

snowflake弱化毫秒的時鐘回撥,默認能容錯5毫秒,避免潤秒問題要在外部調用100次。弱依賴Zookeeper分配workerid.  返回結果用Result結構包裝,會影響一點性能。時鐘回撥時會不可用。

workerid有重複時,會有重號風險。workerid分配方案提供ZooKeeper,但想改其它分配方案需要改源碼。

 

db號段模式屬於依賴DB的號段模式,一次取一個號段的ID,減少對ID的訪問。

8. 百度uid-generator: 這是基於snowflake方案實現的開源組件. 支持容器重啓,workerid用後即棄,機器id佔22位,最多可支持約419w次機器啓動。

sequence (13 bits)每秒下的併發序列,13 bits可支持每秒8192個併發。

缺點:機器號佔太多位,每個時間點能用的號碼太少。

9. tinyid :屬於依賴DB的號段模式,一次取一個號段的ID,減少對ID的訪問。

全局唯一的long型id

趨勢遞增的id,即不保證下一個id一定比上一個大

非連續性(不支持:連續單調遞增ID

優點:

支持批量獲取id

支持多個db的配置,無單點

適用場景:只關心id是數字,趨勢遞增的系統,可以容忍id不連續,有浪費的場景

缺點:

不適用場景:類似訂單id的業務(因爲生成的id大部分是連續的,容易被掃庫、或者測算出訂單量)

作爲分佈式DB表主鍵,不是特別合適。不支持,連續單調遞增ID,依賴DB

 

以上幾種算法還存在的問題。

依賴DB分段批量獲取的算法,是可以產生全局唯一,且批內連續單調遞增的ID。但多個請求分別調用生成一批,多個批都插入數據到庫,還是不會連續的。強依賴DB。

雪花生成的是不連續,全局唯一,但只能是趨勢遞增的ID,部分區間連續,整體不連續。過於依賴時間。

因此又延伸出以下三種算法。

10. 梨花算法:改進的雪花算法,弱依賴時間,對毫秒不敏感,可以允許2分鐘即120秒的時鐘回撥(實際上可以根據業務場景設置更長或更短的容錯時間),可以輕鬆避免潤秒問題。workerid默認從配置文件獲取,代碼架構可方便擴展workerid分配算法,能處理workerid衝突問題。可提前消費1秒。。。

返回結果用原生long返回,異常使用小於0(<0)的數表示。

workerid有重複時,會有重號風險。應該選擇合適的workerid分配方案避免。

支持批獲取ID號.

 

梨花算法,原文介紹鏈接

源碼

https://github.com/automvc/bee/blob/master/src/main/java/org/teasoft/bee/distribution/GenId.java

 

11. 連續單調遞增全局唯一的ID生成算法SerialUniqueId:不依賴於時間,也不依賴於任何第三方組件,只是啓動時,用一個時間作爲第一個ID設置的種子,設置了初值ID後,就可獲取並遞增ID在一臺DB內與傳統的一樣,連續單調遞增(而不只是趨勢遞增),而代表DB的workerid作爲DB的區號放在高位(類似電話號碼的國際區號),從所有DB節點看,則滿足分佈式DB生成全局唯一ID。本地(C8 I7 16g)1981ms可生成1億個ID號,利用上批獲取,分隔業務,每秒生成過10億個ID號不成問題,能滿足雙11的峯值要求。

    可用作分佈式DB內置生成64位long型ID自增主鍵(數據庫能更新功能,爲我們生成第一個ID最好。不能的話,我們就自己根據算法設置第一個ID)。只要按本算法設置了記錄的ID初值,然後默認讓數據庫表id主鍵自增就可以(如MYSQL)。這樣,不管是多少個節點插入到該DB的數據,記錄的ID都是連續單調遞增的。而從全局看,因爲workerid不一樣,多個數據庫間的ID又不會重複。像中國的號碼是:12345678,美國的號碼也是12345678,但國際區號不一樣,號碼也就不一樣。比如這樣100-12345678,101-12345678。

    有的人設計軟件系統時,即使當初設計的是單體系統,爲了讓以後DB能適應分佈式環境,主鍵不會重複,使用了UUID的字符生成形式,從而犧牲了數字id的優越性能。其實何須這樣呢!只需要按本算法設置了記錄的ID初值,然後默認讓數據庫表id主鍵自增就可以啦。DBid主鍵自增不能用在分佈式場景——這是之前的一種錯誤認識。

可以一次取一批ID(即一個範圍內的ID一次就可以獲取了)。是不是可以取代依賴DB的號段模式呢!!

對於單點問題,提供高可用解決方案。將該算法打包成一個服務替換<依賴DB的號段模式>中的DB即可,服務與服務調用,無數據庫IO開銷,從而提高了性能。 (但這樣就不能連續單調了,但也總比之前的號段模式,要依賴DB好吧!)。

 

  使用國際電話號碼作類比說明。只不過是用二進制還看得比較清楚。

國際區號+本國(地區)號碼

爲什麼在一個國家或地區的電話號碼不會重複?爲什麼在國際上一個國家或地區的電話號碼還是不會重複?國際區號放在電話號碼的最高位部分,只要一國內電話號碼不會重複,不同國家用不同的區號,在國際上,電話號碼還是不會重複。連續單調遞增唯一ID生成算法與這個例子也類似。

絕對全局連續單調遞增的DB表主鍵解決方案??:

絕對全局連續單調遞增的方案不存在。如(1,2,3,…10)分到5個DB,則每個庫的ID爲:

DB1(1,6),  DB2(2,7), DB3(3,8),  DB4(4,9), DB5(5,10)

或者:

DB1(1,2),  DB2(3,4), DB3(5,6),  DB4(7,8), DB5(9,10)

要麼總體有序,在各個庫趨勢遞增;要麼各個庫內連續有序,但各部分DB1<DB2<DB3(因爲區分DB的區號放在高位)。

絕對連續單調遞增,全局唯一的方案,如下:

只能是在新增一個庫時,就分配一個庫的workerid. 然後在初始化表時,設置初始ID開始用的值,以後由DB自動增長。Workerid的分配可統一放在一個配置文件,由工具檢測到某個表是空表,且使用的主鍵對應的是Java的long型時,設置初始ID開始用的值。

當用作表ID的主鍵時,可以在表結構創建時,設置不同數據源表主鍵開始用的ID初值。風險幾乎沒有,保證100%不重號。

 

內存生成時,可間隔一定時間(如3分鐘)緩存當前ID值到文件,供重啓時設置回初值用。新ID初始值=緩存的ID值+間隔時間會生成的ID數量最大值。重啓會浪費一個間隔時間生成的ID號。

 

源碼:

https://github.com/automvc/bee/blob/master/src/main/java/org/teasoft/bee/distribution/GenId.java

https://github.com/automvc/honey/blob/master/src/main/java/org/teasoft/honey/distribution/SerialUniqueId.java

 

12. 不依賴時間的梨花算法OneTimeSnowflakeId:

進一步改進第10點提到的梨花算法

通過代碼調用生成ID插入DB的,需要整體有序性。不依賴時間的梨花算法,Workerid應放在序號的上一段,且應用SerialUniqueId算法,使ID不依賴於時間自動遞增。使用不依賴時間的梨花算法,應保證各節點大概均衡輪流出號,這樣入庫的ID會比較有序,因此每個段號內的序列號不能太多。

支持批獲取ID號。可以一次取一批ID(即一個範圍內的ID一次就可以獲取了)。是不是可以取代依賴DB的號段模式呢!!

可間隔時間緩存時間值到文件,供重啓時設置回初值用。若不緩存,則重啓時,又用目前的時間點,重設開始的ID。只要平均不超過419w/s,重啓時造成的時鐘回撥都不會有影響(但卻浪費了辛苦攢下的每秒沒用完的ID號)。要是很多時間都超過419w/s,證明你有足夠的能力做這件事:間隔時間緩存時間值到文件。

 

詳細代碼,可查看源碼:

https://github.com/automvc/honey/blob/master/src/main/java/org/teasoft/honey/distribution/OneTimeSnowflakeId.java

 

總結1:

應用場景區別:主鍵ID一般需要連續遞增;但訂單ID爲了安全則不需要太有序。

按是否可以浪費ID區別:

1.浪費ID,

2.不浪費ID;

2.1可以不強依賴於時間;用完時間自動加1秒;

2.2可以不強依賴於時間,當將Workid放最高位部分時,則可以在Workerid內連續單調遞增

 

總結2:

分段模式與雪花到底有什麼區別?

一個是依賴DB,一個是依賴時間的.

一個是取的號碼可以一直連續遞增的;一個是趨勢遞增,會因workerid的原因產生的ID號是會跳很大一段的.

依賴於DB的號段模式,當多個節點一起拿號時,最終落庫的ID還是不能連續的。

雪花ID適合做分佈式數據庫表主鍵嗎?它只保證遞增,沒保證連續。

 

能不能找到一種,既不依賴DB,也不依賴時間的算法呢?答案是,肯定有的,找吧!

SerialUniqueId,可以代替分段模式類的算法,隨機丟棄部分批量號碼(成爲不連續)還可以代替雪花類算法。

OneTimeSnowflakeId可以代替雪花類算法(設置每個段開始的第一個ID號碼隨機生成,即可不暴露實際使用的ID數量)。

 

這個都被大家忽略了:

DB表自增ID,也是可以改成分佈式特性的,SerialUniqueId就是!

---------------------------------------

有中關村,有科技園。一個南漂在深圳一線的IT小哥,資深工程師也罷,架構師也罷,我獨喜歡軟件設計!帶你瞭解最前沿的軟件設計思想與技術。

           長按二維碼可關注

          更多重磅文章等着你!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章