分佈式ID生成方案總結整理

1、爲什麼需要分佈式ID?

對於單體系統來說,主鍵ID可能會常用主鍵自動的方式進行設置,這種ID生成方法在單體項目是可行的,但是對於分佈式系統,分庫分表之後,就不適應了,比如訂單表數據量太大了,分成了多個庫,如果還採用數據庫主鍵自增的方式,就會出現在不同庫id一致的情況,雖然是不符合業務的
在這裏插入圖片描述

2、業務系統對分佈式ID有什麼要求?

  • 全局唯一性:ID是作爲唯一的標識,不能出現重複
  • 趨勢遞增:互聯網比較喜歡MySQL數據庫,而MySQL數據庫默認使用InnoDB存儲引擎,其使用的是聚集索引,使用有序的主鍵ID有利於保證寫入的效率
  • 單調遞增:保證下一個ID大於上一個ID,這種情況可以保證事務版本號,排序等特殊需求實現
  • 信息安全:前面說了ID要遞增,但是最好不要連續,如果ID是連續的,容易被惡意爬取數據,指定一系列連續的,所以ID遞增但是不規則是最好的

3、分佈式ID生成方案

  • UUID
  • 數據庫自增
  • 號段模式
  • Redis實現
  • 雪花算法(SnowFlake)
  • 百度Uidgenerator
  • 美團Leaf
  • 滴滴TinyID

3.1 UUID

UUID (Universally Unique Identifier),通用唯一識別碼的縮寫。UUID的標準型式包含32個16進制數字,以連字號分爲五段,形式爲8-4-4-4-12的36個字符,示例: 863e254b-ae34-4371-87da-204b71d46a7b。UUID理論上的總數爲1632=2128,約等於3.4 x 10^38。

  • 優點
    • 性能非常高,本地生成的,不依賴於網絡
  • 缺點
    • 不易存儲,16 字節128位,36位長度的字符串
    • 信息不安全,基於MAC地址生成UUID的算法可能會造成MAC地址泄露,暴露使用者的位置
    • uuid的無序性可能會引起數據位置頻繁變動,影響性能

3.2、數據庫自增

在分佈式環境也可以使用mysql的自增實現分佈式ID的生成,如果分庫分表了,當然不是簡單的設置好auto_increment_incrementauto_increment_offset 即可,在分佈式系統中我們可以多部署幾臺機器,每臺機器設置不同的初始值,且步長和機器數相等。比如有兩臺機器。設置步長step爲2,Server1的初始值爲1(1,3,5,7,9,11…)、Server2的初始值爲2(2,4,6,8,10…)。這是Flickr團隊在2010年撰文介紹的一種主鍵生成策略(Ticket Servers: Distributed Unique Primary Keys on the Cheap

假設有N臺機器,step就要設置爲N,如圖進行設置:
在這裏插入圖片描述

這種方案看起來是可行的,但是如果要擴容,步長step等要重新設置,假如只有一臺機器,步長就是1,比如1,2,3,4,5,6,這時候如果要進行擴容,就要重新設置,機器2可以挑一個偶數的數字,這個數字在擴容時間內,數據庫自增要達不到這個數的,然後步長就是2,機器1要重新設置step爲2,然後還是以一個奇數開始進行自增。這個過程看起來不是很雜,但是,如果機器很多的話,那就要花很多時間去維護重新設置

這種實現的缺陷:

  • ID沒有了單調遞增的特性,只能趨勢遞增,有些業務場景可能不符合
  • 數據庫壓力還是比較大,每次獲取ID都需要讀取數據庫,只能通過多臺機器提高穩定性和性能

3.3、號段模式

這種模式也是現在生成分佈式ID的一種方法,實現思路是會從數據庫獲取一個號段範圍,比如[1,1000],生成1到1000的自增ID加載到內存中,建表結構如:

CREATE TABLE id_generator (
  id int(10) NOT NULL,
  max_id bigint(20) NOT NULL COMMENT '當前最大id',
  step int(20) NOT NULL COMMENT '號段的布長',
  biz_type	int(20) NOT NULL COMMENT '業務類型',
  version int(20) NOT NULL COMMENT '版本號',
  PRIMARY KEY (`id`)
) 

  • biz_type :不同業務類型
  • max_id :當前最大的id
  • step :代表號段的步長
  • version :版本號,就像MVCC一樣,可以理解爲樂觀鎖

等ID都用了,再去數據庫獲取,然後更改最大值

update id_generator set max_id = #{max_id+step}, version = version + 1 where version = # {version} and biz_type = XXX

  • 優點:有比較成熟的方案,像百度Uidgenerator,美團Leaf
  • 缺點:依賴於數據庫實現

3.4、 Redis實現

Redis分佈式ID實現主要是通過提供像INCRINCRBY 這樣的自增原子命令,由於Redis單線程的特點,可以保證ID的唯一性和有序性

這種實現方式,如果併發請求量上來後,就需要集羣,不過集羣后,又要和傳統數據庫一樣,設置分段和步長

優缺點:

  • 優點:Redis性能相對比較好,又可以保證唯一性和有序性
  • 缺點:需要依賴Redis來實現,系統需要引進Redis組件

3.4、 雪花算法(SnowFlake)

Snowflake,雪花算法是由Twitter開源的分佈式ID生成算法,以劃分命名空間的方式將
64-bit位分割成多個部分,每個部分代表不同的含義,64位,在java中Long類型是64位的,所以java程序中一般使用Long類型存儲
在這裏插入圖片描述

  • 第一部分:第一位佔用1bit,始終是0,是一個符號位,不使用

  • 第二部分:第2位開始的41位是時間戳。41-bit位可表示241個數,每個數代表毫秒,那麼雪花算法可用的時間年限是(241)/(1000606024365)=69 年的時間

  • 第三部分:10-bit位可表示機器數,即2^10 = 1024臺機器。通常不會部署這麼多臺機器

  • 第四部分:12-bit位是自增序列,可表示2^12 = 4096個數。覺得一毫秒個數不夠用也可以調大點

  • 優點:雪花算法生成的ID是趨勢遞增,不依賴數據庫等第三方系統,生成ID的效率非常高,穩定性好,可以根據自身業務特性分配bit位,比較靈活

  • 缺點:雪花算法強依賴機器時鐘,如果機器上時鐘回撥,會導致發號重複或者服務會處於不可用狀態。如果恰巧回退前生成過一些ID,而時間回退後,生成的ID就有可能重複。

3.5、 百度Uidgenerator

百度的UidGenerator是百度開源基於Java語言實現的唯一ID生成器,是在雪花算法 snowflake 的基礎上做了一些改進。
引用官網的解釋:

UidGenerator是Java實現的, 基於Snowflake算法的唯一ID生成器。UidGenerator以組件形式工作在應用項目中, 支持自定義workerId位數和初始化策略, 從而適用於docker等虛擬化環境下實例自動重啓、漂移等場景。 在實現上, UidGenerator通過借用未來時間來解決sequence天然存在的併發限制; 採用RingBuffer來緩存已生成的UID, 並行化UID的生產和消費, 同時對CacheLine補齊,避免了由RingBuffer帶來的硬件級「僞共享」問題. 最終單機QPS可達600萬。
在這裏插入圖片描述
Snowflake算法描述:指定機器 & 同一時刻 & 某一併發序列,是唯一的。據此可生成一個64 bits的唯一ID(long)。默認採用上圖字節分配方式:

  • sign(1bit):固定1bit符號標識,即生成的UID爲正數。
  • delta seconds (28 bits):當前時間,相對於時間基點"2016-05-20"的增量值,單位:秒,最多可支持約8.7年
  • worker id (22 bits):機器id,最多可支持約420w次機器啓動。內置實現爲在啓動時由數據庫分配,默認分配策略爲用後即棄,後續可提供複用策略。
  • sequence (13 bits):每秒下的併發序列,13 bits可支持每秒8192個併發。

詳細的,可以參考官網解釋,鏈接:https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md

3.6、 美團Leaf

Leaf這個名字是來自德國哲學家、數學家萊布尼茨的一句話: >There are no two
identical leaves in the world > “世界上沒有兩片相同的樹葉”

Leaf 提供兩種生成的ID的方式:號段模式(Leaf-segment)和snowflake模式(Leaf-snowflake)。你可以同時開啓兩種方式,也可以指定開啓某種方式,默認兩種方式爲關閉狀態。

  • Leaf­segment數據庫方案
    其實就是前面介紹的號段模式的改進,可以引用美團技術博客的介紹:

第一種Leaf-segment方案,在使用數據庫的方案上,做了如下改變: - 原方案每次獲取ID都得讀寫一次數據庫,造成數據庫壓力大。改爲利用proxy server批量獲取,每次獲取一個segment(step決定大小)號段的值。用完之後再去數據庫獲取新的號段,可以大大的減輕數據庫的壓力。 - 各個業務不同的發號需求用biz_tag字段來區分,每個biz-tag的ID獲取相互隔離,互不影響。如果以後有性能需求需要對數據庫擴容,不需要上述描述的複雜的擴容操作,只需要對biz_tag分庫分表就行

表結構設計:

>+-------------+--------------+------+-----+-------------------+-----------------------------+
| Field       | Type         | Null | Key | Default           | Extra                       |
+-------------+--------------+------+-----+-------------------+-----------------------------+
| biz_tag     | varchar(128) | NO   | PRI |                   |                             |
| max_id      | bigint(20)   | NO   |     | 1                 |                             |
| step        | int(11)      | NO   |     | NULL              |                             |
| desc        | varchar(256) | YES  |     | NULL              |                             |
| update_time | timestamp    | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------------+--------------+------+-----+-------------------+-----------------------------+
  • Leaf­snowflake方案
    Leafsnowflake是在雪花算法上改進來的,引用官網技術博客介紹:

Leaf-snowflake方案完全沿用snowflake方案的bit位設計,即是“1+41+10+12”的方式組裝ID號。對於workerID的分配,當服務集羣數量較小的情況下,完全可以手動配置。Leaf服務規模較大,動手配置成本太高。所以使用Zookeeper持久順序節點的特性自動對snowflake節點配置wokerID。Leaf-snowflake是按照下面幾個步驟啓動的:

  • 啓動Leaf-snowflake服務,連接Zookeeper,在leaf_forever父節點下檢查自己是否已經註冊過(是否有該順序子節點)。
  • 如果有註冊過直接取回自己的workerID(zk順序節點生成的int類型ID號),啓動服務。
  • 如果沒有註冊過,就在該父節點下面創建一個持久順序節點,創建成功後取回順序號當做自己的workerID號,啓動服務。

在這裏插入圖片描述
這種方案解決了前面提到的雪花算法的缺陷,官網沒解釋,不過Leaf­snowflake對其進行改進,官網的流程圖
在這裏插入圖片描述
詳細介紹請看官網:https://tech.meituan.com/2017/04/21/mt-leaf.html

3.7、 滴滴TinyID

Tinyid是用Java開發的一款分佈式id生成系統,基於數據庫號段算法實現。Tinyid擴展了leaf-segment算法,支持了多數據庫和tinyid-client

Tinyid也是基於號段算法實現,系統實現圖如下:
在這裏插入圖片描述

  • 優點:方便集成,有成熟的方案和解決實現
  • 缺點:依賴 DB的穩定性,需要採用集羣主從備份的方式提高 DB的可用性
    滴滴TinyID wiki:https://github.com/didi/tinyid/wiki

csdn鏈接

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