對於單體系統來說,主鍵ID可能會常用主鍵自動的方式進行設定,這種ID生成方法在單體專案是可行的,但是對於分散式系統,分庫分表之後,就不適應了,比如訂單表資料量太大了,分成了多個庫,如果還採用資料庫主鍵自增的方式,就會出現在不同庫id一致的情況,雖然是不符合業務的
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。
在分散式環境也可以使用mysql的自增實現分散式ID的生成,如果分庫分表了,當然不是簡單的設定好auto_increment_increment
和 auto_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的一種方法,實現思路是會從資料庫獲取一個號段範圍,比如[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`)
)
等ID都用了,再去資料庫獲取,然後更改最大值
update id_generator set max_id = #{max_id+step}, version = version + 1 where version = # {version} and biz_type = XXX
Redis分散式ID實現主要是通過提供像INCR
和 INCRBY
這樣的自增原子命令,由於Redis單執行緒的特點,可以保證ID的唯一性和有序性
這種實現方式,如果並行請求量上來後,就需要叢集,不過叢集後,又要和傳統資料庫一樣,設定分段和步長
優缺點:
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就有可能重複。
百度的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
Leaf這個名字是來自德國哲學家、數學家萊布尼茨的一句話: >There are no two
identical leaves in the world > 「世界上沒有兩片相同的樹葉」
Leaf 提供兩種生成的ID的方式:號段模式(Leaf-segment)和snowflake模式(Leaf-snowflake)。你可以同時開啟兩種方式,也可以指定開啟某種方式,預設兩種方式為關閉狀態。
第一種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方案完全沿用snowflake方案的bit位設計,即是「1+41+10+12」的方式組裝ID號。對於workerID的分配,當服務叢集數量較小的情況下,完全可以手動設定。Leaf服務規模較大,動手設定成本太高。所以使用Zookeeper持久順序節點的特性自動對snowflake節點設定wokerID。Leaf-snowflake是按照下面幾個步驟啟動的:
- 啟動Leaf-snowflake服務,連線Zookeeper,在leaf_forever父節點下檢查自己是否已經註冊過(是否有該順序子節點)。
- 如果有註冊過直接取回自己的workerID(zk順序節點生成的int型別ID號),啟動服務。
- 如果沒有註冊過,就在該父節點下面建立一個持久順序節點,建立成功後取回順序號當做自己的workerID號,啟動服務。
這種方案解決了前面提到的雪花演演算法的缺陷,官網沒解釋,不過Leafsnowflake對其進行改進,官網的流程圖
詳細介紹請看官網:https://tech.meituan.com/2017/04/21/mt-leaf.html
Tinyid是用Java開發的一款分散式id生成系統,基於資料庫號段演演算法實現。Tinyid擴充套件了leaf-segment演演算法,支援了多資料庫和tinyid-client
Tinyid也是基於號段演演算法實現,系統實現圖如下: