爲什麼需要分佈式ID
在複雜的業務系統中,往往需要對大量的數據和消息進行唯一標識。如電商系統中常見的訂單號。
系統使用前期,可能數據量小,應用和數據庫方面壓力不大,這樣單體架構的基礎上,我們可以直接使用數據庫自增ID及對應的單號生成規則即可滿足。
但隨着用戶量的增加,數據量也隨之增大,我們會隨之調整爲分佈式系統架構,集羣部署應用、數據庫分庫分表等改造,原來簡單的數據庫自增ID已經不能滿足生成ID的全局唯一性。
分佈式ID特性
- 全局唯一性:不能出現重複的ID,最基本的要求
- 趨勢遞增:MySQL InnoDB引擎使用的是聚集索引,由於多數RDBMS使用B-tree的數據結構來存儲索引數據,在主鍵的選擇上面我們應儘量使用有序的主鍵保證寫入性能。
- 單調遞增:保證下一個ID一定大於上一個ID。
- 信息安全:如果ID是連續遞增的,惡意用戶就可以很容易的窺見訂單號的規則,從而猜出下一個訂單號。所以在某些場景下,需要ID無規則。
分佈式ID生成方案
1、UUID:通用唯一識別碼,16個字節128位的長數字
組成部分:
當前時間和時間序列+全局唯一性網卡地址
代碼實現:
public static void main(String[] args) {
String uuid = UUID.randomUUID().toString().replaceAll("-","");
System.out.println(uuid);
}
UUID的生成簡單到只有一行代碼,但UUID卻並不適用於實際的業務需求。
像用作訂單號UUID這樣的字符串沒有絲毫的意義,看不出和訂單相關的有用信息;而對於數據庫來說用作業務主鍵ID,它不僅是太長還是字符串,存儲性能差查詢也很耗時,所以不推薦用作分佈式ID。
優點:
代碼實現簡單,不佔用寬帶,數據遷移不受影響
缺點:
無序,無法保證趨勢遞增,查詢慢,不可讀
2、雪花算法:國外的twitter分佈式下ID生成算法
組成部分:
1bit+41bit+10bit+10bit=62bit
高位隨機+毫秒數+機器碼(數據中心+機器id)+10的流水號
優點:
代碼實現簡單,不佔用寬帶,數據遷移不受影響,低位趨勢遞增
缺點:
多臺服務器時間一定要一樣,無序無法保證趨勢遞增要求
3、Mysql
(1)基於數據庫的auto_increment自增ID
當我們需要一個ID的時候,向表中插入一條記錄返回主鍵ID,但這種方式有一個比較致命的缺點,訪問量激增時MySQL本身就是系統的瓶頸,用它來實現分佈式服務風險比較大,不推薦!
優點:
代碼實現方便,性能不錯,數字排序,可讀性很強
缺點:
受限數據庫,擴展麻煩,插入數據庫才能拿到ID,單點故障問題
(2)基於數據庫集羣模式
對方式(1)做高可用優化,換成主從模式集羣,害怕一個主節點掛掉沒法用,那就做雙主模式集羣,也就是兩個Mysql實例都能單獨的生產自增ID。
那這樣還會有個問題,兩個MySQL實例的自增ID都從1開始,會生成重複的ID怎麼辦?
解決方案:
設置起始值和自增步長
優點:
解決DB單點問題
缺點:
不利於後續擴容,而且實際上單個數據庫自身壓力還是大,依舊無法滿足高併發場景。
(3)基於數據庫的號段模式
號段模式是當下分佈式ID生成器的主流實現方式之一,號段模式可以理解爲從數據庫批量的獲取自增ID,每次從數據庫取出一個號段範圍,例如 (1,1000] 代表1000個ID,具體的業務服務將本號段,生成1~1000的自增ID並加載到內存。
由於多業務端可能同時操作,所以採用版本號version樂觀鎖方式更新,這種分佈式ID生成方式不強依賴於數據庫,不會頻繁的訪問數據庫,對數據庫的壓力小很多。
4、Redis
Redis的所有命令操作都是單線程的,本身提供像 incr 和 increby 這樣的自增原子命令,所以能保證生成的 ID 肯定是唯一有序的。
優點:
不依賴於數據庫,靈活方便,且性能優於數據庫。
數字ID天然排序,對分頁或者需要排序的結果很有幫助。
缺點:
如果系統中沒有Redis,還需要引入新的組件,增加系統複雜度。
需要佔用網絡資源,性能要比本地生成慢。
用redis實現還需要注意一點,要考慮到redis持久化的問題。redis有兩種持久化方式RDB和AOF:
-
RDB會定時打一個快照進行持久化,假如連續自增但redis沒及時持久化,而這會Redis掛掉了,重啓Redis後會出現ID重複的情況。
-
AOF會對每條寫命令進行持久化,即使Redis掛掉了也不會出現ID重複的情況,但由於incr命令的特殊性,會導致Redis重啓恢復的數據時間過長。
5、百度(uid-generator)
uid-generator是由百度技術部開發,項目GitHub地址 https://github.com/baidu/uid-generator
uid-generator是基於Snowflake算法實現的,與原始的snowflake算法不同在於,uid-generator支持自定義時間戳、工作機器ID和 序列號 等各部分的位數,而且uid-generator中採用用戶自定義workId的生成策略。
uid-generator需要與數據庫配合使用,需要新增一個WORKER_NODE表。當應用啓動時會向數據庫表中去插入一條數據,插入成功後返回的自增ID就是該機器的workId數據由host,port組成。
6、美團(Leaf)
Leaf由美團開發,github地址:https://github.com/Meituan-Dianping/Leaf
Leaf同時支持號段模式和snowflake算法模式,可以切換使用。
7、滴滴(Tinyid)
Tinyid由滴滴開發,Github地址:https://github.com/didi/tinyid。
Tinyid是基於號段模式原理實現的與Leaf如出一轍,每個服務獲取一個號段(1000,2000]、(2000,3000]、(3000,4000]
總結
以上基本列出了所有常用的分佈式ID生成方式,其實大致分類的話可以分爲兩類:
一種是類DB型的,根據設置不同起始值和步長來實現趨勢遞增,需要考慮服務的容錯性和可用性。
另一種是類snowflake型,這種就是將64位劃分爲不同的段,每段代表不同的涵義,基本就是時間戳、機器ID和序列數。
每種生成方式都有它自己的優缺點,具體如何使用還要看具體的業務需求。