1. 程式人生 > >【系統設計】分散式唯一ID生成方案總結

【系統設計】分散式唯一ID生成方案總結

目錄

  • 分散式系統中唯一ID生成方案
    • 1. 唯一ID簡介
    • 2. 全域性ID常見生成方案
      • 2.1 UUID生成
      • 2.2 資料庫生成
      • 2.3 Redis生成
      • 2.4 利用zookeeper生成
      • 2.5 雪花演算法生成
      • 2.6 其他生成方式

分散式系統中唯一ID生成方案


在系統設計中,我們經常需要一個全域性唯一的ID來標識一條資料,比如訂單表,商品表的主鍵ID。這個ID往往能影響到資料儲存、索引和查詢等操作的效率。因此這個全域性唯一的ID對系統的可用性和效能至關重要。


1. 唯一ID簡介

在系統設計中,我們經常需要一個全域性唯一的ID來標識一條資料,比如訂單表,商品表的主鍵ID。這個ID往往能影響到資料儲存、索引和查詢等操作的效率。因此這個全域性唯一的ID對系統的可用性和效能至關重要。

全域性唯一ID的特點

  • 全域性唯一性:不能出現重複的ID號,既然是唯一標識,這是最基本的要求;
  • 趨勢遞增:在MySQL InnoDB引擎中使用的是聚集索引,由於多數RDBMS使用B-tree的資料結構來儲存索引資料,在主鍵的選擇上面我們應該儘量使用有序的主鍵保證寫入效能。
  • 單調遞增:保證下一個ID一定大於上一個ID,例如事務版本號、IM增量訊息、排序等特殊需求。
  • 資訊保安:如果ID是連續的,惡意使用者的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號就更危險了,競對可以直接知道我們一天的單量。所以在一些應用場景下,會需要ID無規則、不規則。
  • 高可用性:同時除了對ID號碼自身的要求,業務還對ID號生成系統的可用性要求極高,想象一下,如果ID生成系統癱瘓,這就會帶來一場災難。所以不能有單點故障。
  • 分片支援:可以控制ShardingId。比如某一個使用者的文章要放在同一個分片內,這樣查詢效率高,修改也容易。
  • 長度適中

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

  1. 平均延遲和TP999延遲都要儘可能低;
  2. 可用性5個9;
  3. 高QPS。

以上描述引用自部落格

2. 全域性ID常見生成方案

2.1 UUID生成

UUID (Universally Unique Identifier) 的目的,是讓分散式系統中的所有元素,都能有唯一的辨識資訊,而不需要透過中央控制端來做辨識資訊的指定。如此一來,每個人都可以建立不與其它人衝突的 UUID。在這樣的情況下,就不需考慮資料庫建立時的名稱重複問題。

UUID的標準型式包含32個16進位制數字,以連字號分為五段,形式為8-4-4-4-12的36個字元,示例:550e8400-e29b-41d4-a716-446655440000,到目前為止業界一共有5種方式生成UUID,詳情見IETF釋出的UUID規範 A Universally Unique IDentifier (UUID) URN Namespace。

在Java中我們可以直接使用下面的API生成UUID。

UUID uuid  =  UUID.randomUUID(); 
String s = UUID.randomUUID().toString();

優點:

  • 效能非常高:本地生成,API呼叫方便,沒有網路消耗。

缺點:

  • 儲存成本高:UUID太長,16位元組128位,通常以36長度的字串表示,很多場景不適用。
  • 資訊不安全:基於MAC地址生成UUID的演算法可能會造成MAC地址洩露,這個漏洞曾被用於尋找梅麗莎病毒的製作者位置。
  • ID作為主鍵時在特定的環境會存在一些問題,比如做DB主鍵的場景下,UUID就非常不適用。UUID不是趨勢遞增的,而現階段主流的資料庫主鍵索引都是選用的B+樹索引,對於無序長度過長的主鍵插入效率比較低。

2.2 資料庫生成

以MySQL舉例,利用給欄位設定auto_increment_increment和auto_increment_offset來保證ID自增,每次業務使用下列SQL讀寫MySQL得到ID號。

這種方案的優缺點如下:

優點:

  • 非常簡單,利用現有資料庫系統的功能實現,成本小,有DBA專業維護。
  • ID號單調自增,可以實現一些對ID有特殊要求的業務。

缺點:

  • 強依賴DB,當DB異常時整個系統不可用,屬於致命問題。配置主從複製可以儘可能的增加可用性,但是資料一致性在特殊情況下難以保證。主從切換時的不一致可能會導致重複發號。
  • ID發號效能瓶頸限制在單臺MySQL的讀寫效能。

2.3 Redis生成

當使用資料庫來生成ID效能不夠要求的時候,我們可以嘗試使用Redis來生成ID。這主要依賴於Redis是單執行緒的,所以也可以用生成全域性唯一的ID。可以用Redis的原子操作 INCR和INCRBY來實現。

比較適合使用Redis來生成日切流水號。比如訂單號=日期+當日自增長號。可以每天在Redis中生成一個Key,使用INCR進行累加。

優點:

  • 不依賴於資料庫,靈活方便,且效能優於資料庫。
  • 數字ID天然排序,對分頁或者需要排序的結果很有幫助。

缺點:

  • 如果系統中沒有Redis,還需要引入新的元件,增加系統複雜度。
  • 需要編碼和配置的工作量比較大。
  • Redis單點故障,影響序列服務的可用性。
//程式碼整理
//待新增

2.4 利用zookeeper生成

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

很少會使用zookeeper來生成唯一ID。主要是由於需要依賴zookeeper,並且是多步呼叫API,如果在競爭較大的情況下,需要考慮使用分散式鎖。因此,效能在高併發的分散式環境下,也不甚理想。

2.5 雪花演算法生成

這種方案大致來說是一種以劃分名稱空間(UUID也算,由於比較常見,所以單獨分析)來生成ID的一種演算法,這種方案把64-bit分別劃分成多段,分開來標示機器、時間等,比如在snowflake中的64-bit分別表示如下圖(圖片來自網路)所示:

1-bit的時間可以表示(1L<<41)/(1000L360024*365)=69年的時間,10-bit機器可以分別表示1024臺機器。如果我們對IDC劃分有需求,還可以將10-bit分5-bit給IDC,分5-bit給工作機器。這樣就可以表示32個IDC,每個IDC下可以有32臺機器,可以根據自身需求定義。12個自增序列號可以表示2^12個ID,理論上snowflake方案的QPS約為409.6w/s,這種分配方式可以保證在任何一個IDC的任何一臺機器在任意毫秒內生成的ID都是不同的。

這種方式的優缺點是:

優點:

  • 毫秒數在高位,自增序列在低位,整個ID都是趨勢遞增的。
  • 不依賴資料庫等第三方系統,以服務的方式部署,穩定性更高,生成ID的效能也是非常高的。
  • 可以根據自身業務特性分配bit位,非常靈活。

缺點:

  • 強依賴機器時鐘,如果機器上時鐘回撥,會導致發號重複或者服務會處於不可用狀態。

2.6 其他生成方式

百度的uid-generator:

https://github.com/baidu/uid-generator

美團Leaf:

https://github.com/zhuzhong/id