在分布式系統中,有一些場景需要使用全局唯一 ID ,可以和業務場景有關,比如支付流水號,也可以和業務場景無關,比如分庫分表後需要有一個全局唯一 ID,或者用作事務版本號、分布式鏈路追蹤等等,好的全局唯一 ID 需要具備這些特點:
- 全局唯一:這是最基本的要求,不能重複;
- 遞增:有些特殊場景是必須遞增的,比如事務版本號,後面生成的 ID 一定要大于前面的 ID ;有些場景遞增比不遞增要好,因爲遞增有利于數據庫索引的性能;
- 高可用:如果是生成唯一 ID 的系統或服務,那麽一定會有大量的調用,那麽保證其高可用就非常關鍵了;
- 信息安全:如果 ID 是連續的,那麽很容易被惡意操作或泄密,比如訂單號是連續的,那麽很容易就被看出來一天的單量大概是多少;
- 另外考慮到存儲壓力,ID 當然是越短越好。
那麽分布式場景下有哪些生成唯一 ID 的方案呢?
利用數據庫生成
先說最容易理解的方案,利用數據庫的自增長序列生成:數據庫生成唯一主鍵,並通過服務提供給其他系統;如果是小型系統,數據總量和並發量都不是很大的情況下,這種方案足夠支撐。
如果每次生成一個 ID 可能會對數據庫有壓力,可以考慮一次性生成 N 個 ID 放入緩存中,如果緩存中的 ID 被取光,再通過數據庫生成下一批 ID 。
- 優點: 理解起來最容易,實現起來也最簡單。
- 缺點: 也非常明顯了,每種數據庫的實現不同,如果數據庫需要遷移的話比較麻煩;最大的問題是性能問題,並發量到一定級別的時候這個方法估計會很難滿足性能需求;另外通過數據庫自增生成的 ID 攜帶的信息太少,只能起到一個標識的作用,同時自增 ID 也是連續的。
利用其他組件/軟件/中間件生成
利用 Redis / MongoDB / zookeeper 生成:Redis 利用 incr 和 increby ;MongoDB 的 ObjectId;zk 通過 znode 數據版本;都可以生成全局的唯一標識碼。
我們用 MongoDB 的 ObjectId 來舉例:
{“_id”: ObjectId(“5d47ca7528021724ac19f745”)}
MongoDB 的 ObjectId 共占 12 個字節,其中:
- 3.2 之前的版本(包括 3.2): 4 字節時間戳 + 3 字節機器標識符 + 2 字節進程 ID + 3字節隨機計數器
- 3.2 之後版本: 4 字節時間戳 + 5 字節隨機值 + 3 字節遞增計數器
不管是老版本還是新版本,MongoDB 的 ObjectId 至少都可以保證集群內的唯一,我們可以搭建一個全局唯一 ID 生成的服務,利用 MongoDB 生成 ObjectId 並對外提供服務(MongoDB 的各語言驅動都實現了 ObjectId 的生成算法)。
- 優點: 性能高于數據庫;可以使用集群部署;ID 內自帶一些含義,比如時間戳;
- 缺點: 和數據庫一樣,需要引入對應的組件/軟件,增加了系統的複雜度;最關鍵的是,這兩種方案都意味著生成全局唯一 ID 的系統(服務),會成爲一個單點,在軟件架構中,單獨就意味著風險;如果這個服務出現問題,那麽所有依賴于這個服務的系統都會崩潰掉。
UUID
這個是分布式架構中,生成唯一標識碼最常用的算法。爲了保證 UUID 的唯一性,生成因素包括了MAC地址、時間戳、名字空間(Namespace)、隨機或僞隨機數、時序等元素;UUID 有多個版本,每個版本的算法不同,應用範圍也不同:
- Version 1: 基于時間的 UUID,是通過時間戳 + 隨機數 + MAC地址得到;如果應用直接局域網內使用,可以使用 IP 地址替代 MAC 地址;高度唯一(MAC 地址泄漏,也是一個安全問題)。
- Version 2: DCE 安全的 UUID,把 Version 1 中的時間戳前 4 位置換爲 POSIX 的 UID 或 GID ;高度唯一。
- Version 3: 基于名字的 UUID(MD5),通過計算名字和名字空間的 MD5 散列值得到;一定範圍內唯一。
- Version 4: 隨機 UUID,根據隨機數或僞隨機數生成 UUID;有一定概率重複。
- Version 5: 基于名字的UUID(SHA1),和 Version 3 類似,只是散列值計算使用SHA1算法;一定範圍內唯一。
image
此外,還有很多優秀的互聯網公司也提供了唯一 ID 生成的方案或框架,比如美團開源的 Leaf ,百度開源的 UidGenerator 等等。