天天看點

分布式全局唯一ID生成政策​

在單機場景下,全局唯一的ID可以使用資料庫的自增功能,分布式系統一般是高并發場景,那自然不适合。

一、背景

分布式系統中我們會對一些資料量大的業務進行分拆,如:使用者表,訂單表。因為資料量巨大一張表無法承接,就會對其進行分庫分表。

但一旦涉及到分庫分表,就會引申出分布式系統中唯一主鍵

ID

的生成問題。

1.1 唯一ID的特性

  1. 整個系統

    ID

    唯一;
  2. ID是數字類型,而且是趨勢遞增;
  3. ID簡短,查詢效率快。

1.2 遞增與趨勢遞增

遞增 趨勢遞增
第一次生成的ID為12,下一次生成的ID是13,再下一次生成的ID是14。 什麼是?如:在一段時間内,生成的ID是遞增的趨勢。如:再一段時間内生成的ID在【0,1000】之間,過段時間生成的ID在【1000,2000】之間。但在【0-1000】區間内的時候,ID生成有可能第一次是12,第二次是10,第三次是14。

二、方案

2.1 UUID

UUID

全稱:

Universally Unique Identifier

。标準型式包含32個16進制數字,以連字号分為五段,形式為

8-4-4-4-12

的36個字元,示例:

9628f6e9-70ca-45aa-9f7c-77afe0d26e05

  • 優點:
  1. 代碼實作簡單;
  2. 本機生成,沒有性能問題;
  3. 因為是全球唯一的

    ID

    ,是以遷移資料容易。
  • 缺點:
  1. 每次生成的

    ID

    是無序的,無法保證趨勢遞增;
  2. UUID

    的字元串存儲,查詢效率慢;
  3. 存儲空間大;
  4. ID

    本身無業務含義,不可讀。
  • 應用場景:
  1. 類似生成token令牌的場景;
  2. 不适用一些要求有趨勢遞增的ID場景,不适合作為高性能需求的場景下的資料庫主鍵。
也有線上生成

UUID

的網站,如果你的項目上用到了

UUID

,可以用來生成臨時的測試資料。https://www.uuidgenerator.net/

2.2 MySQL主鍵自增

利用了

MySQL

的主鍵自增

auto_increment

,預設每次

ID

1

  1. 數字化,

    ID

    遞增;
  2. 查詢效率高;
  3. 具有一定的業務可讀。
  1. 存在單點問題,如果

    MySQL

    挂了,就沒法生成

    ID

    了;
  2. 資料庫壓力大,高并發抗不住。

2.3 MySQL多執行個體主鍵自增

這個方案就是解決

MySQL

的單點問題,在

auto_increment

基本上面,設定

step

步長

分布式全局唯一ID生成政策​

如上,每台的初始值分别為

1

,

2

3

...

N

,步長為

N

(這個案例步長為

4

  • 優點:解決了單點問題;
  • 缺點:一旦把步長定好後,就無法擴容;而且單個資料庫的壓力大,資料庫自身性能無法滿足高并發。
  • 應用場景:資料不需要擴容的場景。

2.4 基于Redis實作

  • 單機:

    Redis

    incr

    函數在單機上是原子操作,可以保證唯一且遞增。
  • 叢集:單機

    Redis

    可能無法支撐高并發。叢集情況下,可以使用步長的方式。比如有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
           
  • 優點:有序遞增,可讀性強。
  • 缺點:占用帶寬,每次要向

    Redis

    進行請求。

三、優化方案

3.1、改造資料庫主鍵自增

資料庫的自增主鍵的特性,可以實作分布式ID,适合做userId,正好符合如何永不遷移資料和避免熱點? 但這個方案有嚴重的問題:

  1. 一旦步長定下來,不容易擴容;
  2. 資料庫壓力山大。
  • 為什麼壓力大?

因為我們每次擷取ID的時候,都要去資料庫請求一次。那我們可以不可以不要每次去取?

可以請求資料庫得到ID的時候,可設計成獲得的ID是一個ID區間段。

分布式全局唯一ID生成政策​
  • 上圖

    ID

    規則表含義:
  1. id

    表示為主鍵,無業務含義;
  2. biz_tag

    為了表示業務,因為整體系統中會有很多業務需要生成

    ID

    ,這樣可以共用一張表維護;
  3. max_id

    表示現在整體系統中已經配置設定的最大

    ID

    ;
  4. desc

    描述;
  5. update_time

    表示每次取的

    ID

    時間;
  • 整體流程:
  1. 【使用者服務】在注冊一個使用者時,需要一個使用者

    ID

    ;會請求【生成

    ID

    服務(是獨立的應用)】的接口;
  2. 【生成

    ID

    服務】會去查詢資料庫,找到

    user_tag

    id

    ,現在的

    max_id

    為 ,

    step=1000

  3. ID

    服務】把

    max_id

    step

    傳回給【使用者服務】;并且把

    max_id

    更新為

    max_id = max_id + step

    ,即更新為

    1000

  4. 【使用者服務】獲得

    max_id=0

    step=1000

  5. 這個使用者服務可以用

    ID=【max_id + 1,max_id+step】

    區間的

    ID

    ,即為

    【1,1000】

  6. 【使用者服務】會把這個區間儲存到

    jvm

    中;
  7. 【使用者服務】需要用到

    ID

    的時候,在區間

    【1,1000】

    中依次擷取

    ID

    ,可采用

    AtomicLong

    中的

    getAndIncrement

    方法;
  8. 如果把區間的值用完了,再去請求【生産

    ID

    服務】接口,擷取到

    max_id

    1000

    ,即可以用

    【max_id + 1,max_id+step】

    ID

    【1001,2000】

  9. 該方案就非常完美的解決了資料庫自增的問題,而且可以自行定義

    max_id

    的起點,和

    step

    步長,非常友善擴容;
  10. 也解決了資料庫壓力的問題,因為在一段區間内,是在

    jvm

    記憶體中擷取的,而不需要每次請求資料庫。即使資料庫當機了,系統也不受影響,

    ID

    還能維持一段時間。

3.2 競争問題

以上方案中,如果是多個使用者服務,同時擷取

ID

,同時去請求【ID服務】,在擷取

max_id

的時候會存在并發問題。如:

使用者服務

A

,取到的

max_id=1000

;使用者服務

B

取到的也是

max_id=1000

,那就出現了問題,

ID

重複了。

解決方案是:加分布式鎖,保證同一時刻隻有一個使用者服務擷取

max_id

3.3 突發阻塞問題

分布式全局唯一ID生成政策​

因為競争問題,所有隻有一個使用者服務去操作資料庫,其他二個會被阻塞。出現的現象就是一會兒突然系統耗時變長,怎麼去解決?

  • buffer

    方案
分布式全局唯一ID生成政策​

流程如下:

  1. 目前擷取

    ID

    buffer1

    中,每次擷取

    ID

    buffer1

    中擷取;
  2. buffer1

    ID

    已經使用到了

    100

    ,也就是達到區間的

    10%

  3. 達到了

    10%

    ,先判斷

    buffer2

    中有沒有去擷取過,如果沒有就立即發起請求擷取

    ID

    線程,此線程把擷取到的

    ID

    ,設定到

    buffer2

  4. 如果

    buffer1

    用完了,會自動切換到

    buffer2

  5. buffer2

    用到

    10%

    了,也會啟動線程再次擷取,設定到

    buffer1

  6. 依次往返。

3.4 總結

  1. buffer

    的方案就達到了業務場景用的

    ID

    ,都是在

    jvm

    記憶體中獲得的,從此不需要到資料庫中擷取了,資料庫當機時長長點兒也沒太大影響了。
  2. 因為會有一個線程,會觀察什麼時候去自動擷取。兩個

    buffer

    之間自行切換使用,就解決了突發阻塞的問題。

四、其他方式

還有一些其他的

ID

生成方案,比如:

  1. 滴滴:時間+起點編号+車牌号;
  2. 淘寶訂單:時間戳+使用者

    ID

  3. 其他電商:時間戳+下單管道+使用者

    ID

    ,有的會加上訂單第一個商品的

    ID

  4. MongoDB

    ID

    :通過

    時間+機器碼+pid+inc

    共12個位元組,

    4+3+2+3

    的方式最終辨別成一個24長度的十六進制字元。

技術交流,歡迎掃一掃!

風塵部落格

分布式全局唯一ID生成政策​

繼續閱讀