分散式:分散式系統下的唯一序列

翁智華 發表於 2022-01-24

1 介紹

在分散式系統中,由於涉及到多個不同業務module的互動,以及高併發的場景。我們需要系統能夠生成一個跨業務module的全網唯一序列號,來保證我們業務操作的獨立性和唯一性。 

在常見的業務場景中,比如全域性訂單Id,唯一標識的支付編號等,都需要這個來保證。

那生成ID都有哪些解決方案呢?特別是在複雜的分散式系統業務場景中,我們應該採用哪種解決方案來實現這個唯一序列呢?

一般來說,這個唯一序號有如下幾種特徵:

  • 全域性唯一性:確保生成的序列是全域性唯一的,不可重複。

  • 有序性:確保生成的ID值對於某個使用者或者業務是按一定的數字有序遞增的。

  • 高可用性:確保生成ID功能的高可用,能夠承接較大峰值,能夠保證序列生成的有效性(不重複且有序)。

  • 帶時間標記:ID中有時間片段組成,可是清晰識別出操作的時間。

下面是業內幾種常見的分散式唯一序列生成方案,我們一一來介紹下。

2 資料庫自增

資料庫主鍵設定自增序號 auto_increment,可以按照一定的趨勢自增,保證主鍵ID的唯一性。

這個方案簡單易操作,優點是明顯、可控。

但由於它是在資料庫的單表上進行操作,對資料庫效能依賴比較明顯,高併發下的壓力也很大。所以不是唯一ID生成的最佳方法。

1 create table `t_generator_id` 
2 ( 
3 `id` bigint(20) not null auto_increment,  -- 表示自增列 
4  -- 其他欄位資訊
5 )

3 系統時間毫秒數

我們可以使用當前系統時間精確到毫秒數(或者時間戳)+業務屬性+使用者屬性+隨機數+...等引數組合形式來確保ID的唯一性,缺點是ID的有序性難以保證,如果對有序性由強需求的業務不建議使用。

類似京東淘寶等電商的訂單號生成。因為訂單號和使用者id在業務上的區別,訂單號儘可能要多些冗餘的業務資訊,比如

滴滴:時間+起點編號+車牌號 ; 淘寶訂單:時間戳+使用者ID,類似滴滴訂單的唯一序號如下:

分散式:分散式系統下的唯一序列

4 UUID(GUID)

Java自帶的生成UUID的方式(.Net體系下也有GUID可以對應),生成的是Length=32的16進位制格式的字串,如果回退為byte陣列共16個byte元素,即UUID是一個128bit長的數字,一般用16進製表示。

可以保證唯一性,但缺點是它不包含時間標識、業務資料可讀性太差了,而且也不能ID的有序遞增。優點生成方式,簡單,高效,一般業務系統中比較少用。

5 批量預生成ID

1、在記憶體(快取)中,按需批量生成N個ID,並將最大ID值記錄到資料庫中。比如生成 1~10000,把max=10000持久化到資料庫中,記憶體中記錄的是current=1和max=10000。

2、所有的使用都在記憶體中進行,每消耗一次序號,current + 1。

3、當current==max的時候,重複第一個步驟,再次批量生成 10001~20000的值,並將資料庫中的max改成20000。

優點是避免了每次生成ID都要訪問資料庫並帶來壓力。

缺點是隻能是單點服務,如果服務重啟勢必會造成ID丟失不連續的情況,而且這種方式也不利於水平擴充套件。

分散式:分散式系統下的唯一序列 

6 Redis生成唯一序列

Redis可以使用簡易的String型別,它的 incr/decr key 語法,支援高效快速的增減值,能夠保證生成的ID肯定是唯一有序的。

這種方式不依賴資料庫持久化,速度快,算是比較好的辦法了。但系統中引入Redis這一中介軟體,無形中增加維護成本。在超大流量、超高併發的情況下,單例項Redis還是無法滿足的,需要橫向擴充套件Redis叢集來進行支撐。

1 incr/decr key // 自增減 1
2 incrby/decrby key increment  // 自增減指定數值
3 incrbyfloat/decrbyfloat key increment  // 自增減浮點數 

還可以利用像Zookeeper中的znode資料版本來生成序列號,及MongoDB的ObjectId等,但是效能不如Redis,不是很推薦。

7 snowflake演算法

Twitter在把儲存系統從MySQL遷移到Cassandra的過程中由於Cassandra沒有順序ID生成機制,於是自己開發了一套全域性唯一ID生成服務:Snowflake。

分散式:分散式系統下的唯一序列 

如上圖的所示,Twitter的snowflake演算法下面幾部分組成:

  • 41位的時間序列,精確到毫秒,可以使用69年

  • 10位的機器標識,最多支援部署1024個節點

  • 12位的序列號,支援每個節點每毫秒產生4096個ID序號,最高位是符號位始終為0。

這種方案效能好,在單機上是遞增的,但是由於涉及到分散式環境,每臺機器上的時鐘不可能完全同步,也許有時候也會出現不是全域性遞增的情況。

而且這個專案在2010就停止維護了,但這個設計思路被很多廠家參考,應用於各個業務的ID生成器及變種。

8 UidGenerator

UidGenerator是百度開源的一款分散式高效能的唯一ID生成器,使用Java實現的, 基於Snowflake演算法的唯一ID生成器。

在實現上, UidGenerator通過借用未來時間來解決sequence天然存在的併發限制; 採用RingBuffer來快取已生成的UID, 並行化UID的生產和消費, 同時對CacheLine補齊,避免了由RingBuffer帶來的硬體級「偽共享」問題. 最終單機QPS可達600萬。

具體的GitHub地址如下:

https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md

9 Leaf

Leaf是美團開源的分散式ID生成器,能保證全域性唯一性、趨勢遞增、單調遞增、資訊保安,同時也需要依賴關聯式資料庫、Zookeeper等中介軟體。

美團技術社群有詳細的說明,同時也對分散式ID生成有一些比較好的分析和建議:

https://tech.meituan.com/2017/04/21/mt-leaf.html

10 總結

個人覺得最好的是Redis方案和snowflake演算法,無論是效能還是可用性程度上。另外各大廠也有自己的一些做法,比如百度的UidGenerator 和 美團的Leaf,

主要也是根據現有的方案進行優化和改造,達到比較契合他們自己業務的目標。