常見的分佈式Id生成器剖析

在高併發或者分表分庫情況下怎麼保證數據id的冪等性呢?

經常用到的解決方案有以下幾種。

微軟公司通用唯一識別碼(UUID)
Twitter公司雪花算法(SnowFlake)
基於數據庫的id自增
對id進行緩存

一、SnowFlake算法

snowflake是Twitter開源的分佈式ID生成算法,結果是一個long型的ID。

其核心思想是:使用41bit作爲毫秒數,10bit作爲機器的ID(5個bit是數據中心,5個bit的機器ID),12bit作爲毫秒內的流水號,最後還有一個符號位,永遠是0。

snowflake算法所生成的ID結構,如下圖:

整個結構是64位,所以我們在Java中可以使用long來進行存儲。

該算法實現基本就是二進制操作,單機每秒內理論上最多可以生成1024*(2^12),也就是409.6萬個ID(1024 X 4096 = 4194304)

  • 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

  • 1位標識,由於long基本類型在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0

  • 41位時間截(毫秒級)。注意,41位時間截不是存儲當前時間的時間截,而是存儲時間截的差值(當前時間截 - 開始時間截)

  • 這裏的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程序來指定的(如下面程序IdWorker類的startTime屬性)。41位的時間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69

  • 10位的數據機器位,可以部署在1024個節點,包括5位datacenterId和5位workerId

  • 12位序列,毫秒內的計數,12位的計數順序號支持每個節點每毫秒(同一機器,同一時間截)產生4096個ID序號

  • 加起來剛好64位,爲一個Long型。

  • SnowFlake的優點是,整體上按照時間自增排序,並且整個分佈式系統內不會產生ID碰撞(由數據中心ID和機器ID作區分),並且效率較高

    經測試,SnowFlake每秒能夠產生26萬ID左右。

snowFlake算法的優點:

  1. 生成ID時不依賴於DB,完全在內存生成,高性能高可用。

  2. ID呈趨勢遞增,後續插入索引樹的時候性能較好。

SnowFlake算法的缺點:

依賴於系統時鐘的一致性。如果某臺機器的系統時鐘回撥,有可能造成ID衝突,或者ID亂序

二、基於數據庫的id自增

 

字段說明:

  • id代表該obj本次set後的maxid
  • 不同業務不同的ID需求可以用obj字段區分,每個obj的ID獲取相互隔離,互不影響
  • step 步長,代表每次獲取多長ID段到緩存

基本要求:

  • 全局唯一性:不能出現重複的ID號,既然是唯一標識,這是最基本的要求
  • 趨勢遞增:在MySql InnoDB引擎使用的是聚集索引, 由於多數的RDBMS使用B-tree的數據結構來存儲索引數據,在主鍵的選擇上應該儘量使用有序的主鍵保證寫入性能
  • 單調遞增:保證下一個ID一定大於上一個ID,例如事務版本號、IM增量消息、排序等特殊需求
  • 信息安全:如果ID是聯繫的,惡意用戶的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號就更危險了,競爭對手可以直接知道我們一天的單量。所以在這些場景下,會需要ID無規則,不規則。

性能要求:

  • 平均延遲和TP999延遲都要儘可能低
  • 可用性5個9(99.999%)
  • 高QPS

優點:

  • 支持全局唯一、系統唯一、表級別唯一三種形式,絕對不會出現重複ID,且ID整體趨勢遞增;
  • 大大的降低了數據庫的壓力,ID生成可以做到每秒生成幾萬幾十萬個
  • 一定的高可用,服務採用預分配ID的方案,每次調用分配10000個id到系統緩存集羣,即使MySQL宕機,也能容忍一段時間數據不可用;
  • 接入簡單,直接通過公司的RPC服務或者HTTP調用即可使用;

缺點:

  • 強依賴數據庫,當MySQL服務長時間不可用,那麼對公司業務將是一場災難;
  • 併發性能較低,假設公司業務量急劇增長,idgenerator服務請求併發量增高,而實際上在更新數據庫時會觸發表鎖,可能造成ID獲取失敗,導致服務不可用;
  • 缺少服務自身監控,無法通過web層的內存數據映射界面實時觀測所有號段的下發狀態及使用情況
  • 服務仍然是單點

  • 如果服務掛了,服務重啓起來之後,繼續生成ID可能會不連續,中間出現空洞(服務內存是保存着0,1,2,3,4,5,數據庫中max-id是5,分配到3時,服務重啓了,下次會從6開始分配,4和5就成了空洞,不過這個問題也不大)

  • 雖然每秒可以生成幾萬幾十萬個ID,但畢竟還是有性能上限,無法進行水平擴展

三、UUID

UUID 是指Universally Unique Identifier,翻譯爲中文是通用唯一識別碼,UUID 的目的是讓分佈式系統中的所有元素都能有唯一的識別信息。如此一來,每個人都可以創建不與其它人衝突的 UUID,就不需考慮數據庫創建時的名稱重複問題。

定義

UUID 是由一組32位數的16進制數字所構成,是故 UUID 理論上的總數爲1632=2128,約等於3.4 x 10123。

也就是說若每納秒產生1百萬個 UUID,要花100億年纔會將所有 UUID 用完

格式

UUID 的十六個八位字節被表示爲 32個十六進制數字,以連字號分隔的五組來顯示,形式爲 8-4-4-4-12,總共有 36個字符(即三十二個英數字母和四個連字號)。例如:

123e4567-e89b-12d3-a456-426655440000

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

數字 M的四位表示 UUID 版本,當前規範有5個版本,M可選值爲1, 2, 3, 4, 5

數字 N的一至四個最高有效位表示 UUID 變體( variant ),有固定的兩位10xx因此只可能取值8, 9, a, b

UUID版本通過M表示,當前規範有5個版本,M可選值爲1, 2, 3, 4, 5。這5個版本使用不同算法,利用不同的信息來產生UUID,各版本有各自優勢,適用於不同情景。具體使用的信息

  • version 1, date-time & MAC address
  • version 2, date-time & group/user id
  • version 3, MD5 hash & namespace
  • version 4, pseudo-random number
  • version 5, SHA-1 hash & namespace

使用較多的是版本1和版本4,其中版本1使用當前時間戳和MAC地址信息。版本4使用(僞)隨機數信息,128bit中,除去版本確定的4bit和variant確定的2bit,其它122bit全部由(僞)隨機數信息確定。

因爲時間戳和隨機數的唯一性,版本1和版本4總是生成唯一的標識符。若希望對給定的一個字符串總是能生成相同的 UUID,使用版本3或版本5。

隨機 UUID 的重複機率

Java中 UUID 使用版本4進行實現,所以由java.util.UUID類產生的 UUID,128個比特中,有122個比特是隨機產生,4個比特標識版本被使用,還有2個標識變體被使用。利用生日悖論,可計算出兩筆 UUID 擁有相同值的機率約爲

其中x爲 UUID 的取值範圍,n爲 UUID 的個數。

以下是以 x = 2122 計算出n筆 UUID 後產生碰撞的機率:

n 機率
68,719,476,736 = 236 0.0000000000000004 (4 x 10-16)
2,199,023,255,552 = 241 0.0000000000004 (4 x 10-13)
70,368,744,177,664 = 246 0.0000000004 (4 x 10-10)

換句話說,每秒產生10億筆 UUID ,100年後只產生一次重複的機率是50%。如果地球上每個人都各有6億筆 UUID,發生一次重複的機率是50%。與被隕石擊中的機率比較的話,已知一個人每年被隕石擊中的機率估計爲170億分之1,也就是說機率大約是0.00000000006 (6 x 10-11),等同於在一年內生產2000億個 UUID 併發生一次重複。

 

參考文檔:

1.  雪花算法(Twitter ): https://github.com/souyunku/SnowFlake 

2.  Tinyid(滴滴): https://github.com/didi/tinyid

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

4.  UidGenerator(百度):https://github.com/baidu/uid-generator

5.  Leaf (美團):https://github.com/Meituan-Dianping/Lea

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