分佈式場景下的ID生成解決方案

在服務設計中,常常遇到的一個問題就是如何生成一個全局惟一的ID,例如訂單號,流水號等。對於ID的要求主要有如下幾點:
java

  1. 全局惟一,不會存在衝突;mysql

  2. 快速生成,可以知足高併發場景下的需求;git

  3. 可以知足分佈式場景下的業務需求;github

  4. ID生成服務可以方便的擴容縮容。redis

  5. 最好基本有序;算法

  6. 可以附加一些業務信息,例如時間,系統標識等;sql

  7. 可以應對測試環境的一些特殊需求,如跳日,日期回撥等。數據庫

咱們簡單分析下常見的實現方式:安全

UUID網絡

最熟悉的應該是UUID,UUID 是 通用惟一識別碼(Universally Unique Identifier)的縮寫。按照UUID規範,UUID的實現方式一共有四種:

基於時間戳的UUID。這個UUID是基於時間戳,隨機數和當前機器mac地址計算獲得的,能夠保證全球範圍內的惟一性。可是,使用mac地址爲帶來安全問題。

DCE(Distributed Computing Environment)安全的UUID和基於時間的UUID算法相同,但會把時間戳的前4位置換爲POSIX的UID或GID。

基於名字的UUID(MD5),經過計算名字和名字空間的MD5散列值獲得。這個版本的UUID保證了:相同名字空間中不一樣名字生成的UUID的惟一性;不一樣名字空間中的UUID的惟一性;相同名字空間中相同名字的UUID重複生成是相同的。

根據隨機數,或者僞隨機數生成UUID。這個是存在重複機率的,雖然機率很小,可是仍是存在的。

基於名字的UUID(SHA1),這個與第三種相似。

以java爲例,經常使用的java.util.UUID這個類支持第3、四兩種UUID的生成方法:

如源碼所示,分別是隨機UUID和基於名字的UUID。

UUID是優勢在於使用相對簡單,每一個服務本身生成。

缺點我認爲主要有幾個:

  1. 生成的ID是隨機的,不能從字面上看出一些附加信息。

  2. 索引效率比較低;

  3. 不知足基本有序;

  4. 存儲佔用空間大,這個在目前看來不是主要問題。

數據庫自增主鍵

數據庫提供了一種自增主鍵的方式來生成ID,這種方式的主要優勢是生成簡單,ID是嚴格有序的。

方式比較簡單,這裏再也不贅述。

可能存在問題的地方我認爲主要有幾點:

  1. 在分庫分表場景下不太合適。第一個問題是存在多庫的場景下可能存在ID衝突的問題,雖然能夠經過設定步長解決,可是不利於數據庫擴展;

  2. 數據庫自增ID存在一個上限,mysql默認的應該是Int,默認長度是32位。大概是幾十億,這個上限應該很容易達到。

  3. 數據庫壓力大。每次生成ID都須要讀寫數據庫,數據庫壓力較大,容易成爲瓶頸。

基於redis實現

Redis 的 INCR 命令支持 「INCR AND GET」 原子操做。利用這個特性,咱們能夠在 Redis 中存序列號,讓分佈式環境中多個取號服務在 Redis 中經過 INCR 命令來實現取號;同時 Redis 是單進程單線程架構,不會由於多個取號方的 INCR 命令致使取號重複。所以,基於 Redis 的 INCR 命令實現序列號的生成基本能知足全局惟一與單調遞增的特性,而且性能還不錯。

可是不足的地方是不可以附加一些業務信息,例如時間,業務系統信息等。

基於ZOOKEEPER實現

下圖是一個經典的基於zk實現的ID生成器的解決方案,參考了網友的實現:

這個方案的缺點也很明顯,沒法附加業務信息,且只能產生32位的ID。

SnowFlake

SnowFlake是Twitter開源的一個全局ID生成算法,長度爲64位,在java中恰好是一個long型。

SnowFlake中各個bit位的含義以下圖(圖片來自於網絡)所示:

圖片

主要分爲四段:

第一位是0,暫時未使用;

接下來是41位,表示與1970-01-01 00:00:00:000的毫秒時間數差,也能夠指定時間,夠用69年;

接下來10位表示集羣ID和機器實例ID,最大支持1024個實例;

最後12位表示同一毫秒內的序列號,最多支持4096個,也就是說每毫米最多生成4096個全局ID。

這裏提供了一種思路,具體的實現咱們能夠參考,也能夠根據需求去改進本身的實現,例如每段含義能夠本身修改或擴充。

這種方案有個缺點:在作業務測試的時候常常會出現跳日和時鐘回撥的狀況,這種狀況下,生成的ID是會發生衝突的。建議解決方案時衝突時直接拋出異常,從新生成。

美團的Leaf

這個是美團開源的全局ID生成器,取自於這個世界上沒有兩片徹底相同的葉子。主要有如下幾個特色:

  • 全局惟一,絕對不會出現重複的ID,且ID總體趨勢遞增。

  • 高可用,服務徹底基於分佈式架構,即便MySQL宕機,也能容忍一段時間的數據庫不可用。

  • 高併發低延時。

  • 接入簡單。

這個算法在美團內部已經迭代了不少版本,這裏簡單介紹下第一個版本的簡單實現,具體深刻的研究能夠參考github上開源的代碼。

圖片

Leaf是基於分佈式架構的,即一個數據庫上掛了N個server,ID的生成採用預發的方式,每次server啓動時會去數據庫拿一批固定長度的ID,而後把最大的ID持久化在數據庫中,也就是說並非每一個ID都須要持久化,能夠減輕數據庫壓力。

同時Leaf除了上述的號段模式以外還支持SnowFlake模式,能夠根據本身須要選擇。

總結

其實沒有所謂的最優的解決方案,在平常的使用中咱們須要根據本身的具體業務場景選擇合適的ID生成方式,若是業務比較簡單,徹底能夠採用UUID或者是mysql自增主鍵的方式,若是業務場景複雜,則須要根據業務場景的特色做出權衡。

相關文章
相關標籤/搜索