分佈式全局唯一ID 方案 (附參考角度)

分佈式架構下,唯一序列號生成是我們在設計一個系統,尤其是數據庫使用分庫分表的時候常常會遇見的問題。當分成若干個sharding表後,如何能夠快速拿到一個唯一序列號,是經常遇到的問題。

現目前市面上有很多的解決方案,那麼我們應該怎麼選擇?

首先思考🤔你需要的全局唯一ID 需要具備什麼特性?(根據需求來定,一般從這些方面考慮)

  • 全局唯一
  • 支持高併發
  • 能夠體現一定屬性
  • 高可靠,容錯單點故障
  • 高性能,每秒能生成數量
  • 是否要求不可推算準確唯一ID(部門可推算+部分不可推算部分)

以下是一些基本的方式,供參考,根據實際需求自己調整。

UUID

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

優點:

  • 1)簡單,代碼方便。
  • 2)生成ID性能非常好,基本不會有性能問題。
  • 3)全球唯一,在遇見數據遷移,系統數據合併,或者數據庫變更等情況下,可以從容應對。

缺點:

  • 1)沒有排序,無法保證趨勢遞增。
  • 2)UUID往往是使用字符串存儲,查詢的效率比較低。
  • 3)存儲空間比較大,如果是海量數據庫,就需要考慮存儲量的問題。
  • 4)傳輸數據量大
  • 5)不可讀。

優化方案:

  • 1)爲了解決UUID不可讀,可以使用UUID to Int64的方法。
  • 2)爲了解決UUID無序的問題,NHibernate在其主鍵生成方式中提供了Comb算法(combined guid/timestamp)。保留GUID的10個字節,用另6個字節表示GUID生成的時間(DateTime)。

數據庫自增長序列或字段

最常見的方式。利用數據庫,全數據庫唯一。

優點

  • 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生成數據庫操作的負載。

Redis生成ID

當使用數據庫來生成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)需要編碼和配置的工作量比較大。

Twitter的snowflake算法

snowflake是Twitter開源的分佈式ID生成算法,結果是一個long型的ID。其核心思想是:使用41bit作爲毫秒數,10bit作爲機器的ID(5個bit是數據中心,5個bit的機器ID),12bit作爲毫秒內的流水號(意味着每個節點在每毫秒可以產生 4096 個 ID),最後還有一個符號位,永遠是0。

java版本代碼可以參考:分佈式唯一ID 雪花算法(JAVA)

snowflake算法可以根據自身項目的需要進行一定的修改。比如估算未來的數據中心個數,每個數據中心的機器數以及統一毫秒可以能的併發數來調整在算法中所需要的bit數。

優點:

  • 1)不依賴於數據庫,靈活方便,且性能優於數據庫。
  • 2)ID按照時間在單機上是遞增的。

缺點:

  • 1)在單機上是遞增的,但是由於涉及到分佈式環境,每臺機器上的時鐘不可能完全同步,也許有時候也會出現不是全局遞增的情況。

利用業務字段

還有其他一些方案,比如京東淘寶等電商的訂單號生成。因爲訂單號和用戶id在業務上的區別,訂單號儘可能要多些冗餘的業務信息,比如:

滴滴:時間+起點編號+車牌號

淘寶訂單:時間戳+用戶ID

其他電商:時間戳+下單渠道+用戶ID,有的會加上訂單第一個商品的ID。

可以根據自己業務裏面的實際情況,找到相應的 不重複的標識進行全局唯一ID生成

 利用zookeeper生成唯一ID

zookeeper主要通過其znode數據版本來生成序列號,可以生成32位和64位的數據版本號,客戶端可以使用這個版本號來作爲唯一的序列號。很少會使用zookeeper來生成唯一ID。主要是由於需要依賴zookeeper,並且是多步調用API,如果在競爭較大的情況下,需要考慮使用分佈式鎖。因此,性能在高併發的分佈式環境下,也不甚理想。 

MongoDB的ObjectId MongoDB的ObjectId和snowflake算法類似。

它設計成輕量型的,不同的機器都能用全局唯一的同種方法方便地生成它。MongoDB 從一開始就設計用來作爲分佈式數據庫,處理多個節點是一個核心要求。使其在分片環境中要容易生成得多。 

前4 個字節是從標準紀元開始的時間戳,單位爲秒。時間戳,與隨後的5 個字節組合起來,提供了秒級別的唯一性。由於時間戳在前,這意味着ObjectId 大致會按照插入的順序排列。這對於某些方面很有用,如將其作爲索引提高效率。這4 個字節也隱含了文檔創建的時間。絕大多數客戶端類庫都會公開一個方法從ObjectId 獲取這個信息。 
接下來的3 字節是所在主機的唯一標識符。通常是機器主機名的散列值。這樣就可以確保不同主機生成不同的ObjectId,不產生衝突。 
爲了確保在同一臺機器上併發的多個進程產生的ObjectId 是唯一的,接下來的兩字節來自產生ObjectId 的進程標識符(PID)。 
前9 字節保證了同一秒鐘不同機器不同進程產生的ObjectId 是唯一的。後3 字節就是一個自動增加的計數器,確保相同進程同一秒產生的ObjectId 也是不一樣的。同一秒鐘最多允許每個進程擁有2563(16 777 216)個不同的ObjectId。

小結

如何選擇合適的分佈式全局唯一ID 問題根據自己的實際項目情況來判定,不要僅僅侷限在一種方案,可以靈活的使用,或者混合使用,只要理解生成原理,基本都沒問題

 

參考

https://blog.csdn.net/rainyear/article/details/86293122

 

 

 

 

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