跳轉到

Unique ID Generation

目前業界有哪些 ID 的生成方式?

UUID 模式

uuid 全名為通用唯一識別碼(Universally Unique Identifier)。底層是透過 mac 位址+時間+隨機數進行產生的 128 位元的二進制,轉換為 16 進位字串後,長度為 32 位元。

優點: uuid 產生的 ID 能滿足唯一性;且不依賴任何中間件;產生速度超快。

但是 uuid 的缺點也很明顯: 產生的 ID 太長了,字元長度有 32 位元

在業務系統裡,一般 ID 是要進行儲存到庫裡的,並且很有可能會作為常用的查詢條件,例如儲存到 mysql,比 int32 的 4 位元組或 int64 的 8 位元組來講,太佔空間;

第二個缺點:就是生成的 ID 是無序的,不是遞增的;這樣在把 ID 做為索引字段,在底層插入索引時,還會引起索引節點的分裂,影響插入速度。所以用 UUID 產生的 ID,對資料庫來說很不友善

那麼 UUID 是不是就沒有業務使用場景了呢?
不是的,像很多大廠的日誌連結追蹤的 ID,就是基於 UUID 進行產生的。

redis 自增模式

大家都知道在 redis 裡有一個 INCR 指令,能從 0 到 Long.maxValue-1 進行單一連續的自增,最大 19 位;9 位數字就是億基本的數了,還剩 10 位,假設每天 1000W 的訂單數據,即 1000W 個 ID,能支援 25 億年;距離地球毀滅時間還差點

優點:
redis 自增產生的 ID 能滿足唯一性也能滿足單調自增性。做底層資料儲存或索引的時候,對資料庫也很友善。生成速度還飛快,因為都是在記憶體中產生。

缺點:
redis 自增 ID 的缺點也很明顯,即可能會丟數據 ,redis 是一個 kv 內存數據庫,雖提供了異步複製數據+哨兵模式來保證服務的高可用+高可靠;但 redis 不保證不丟數據,即使從庫變為主庫,但也不保證從庫數據是最新的,畢竟是異步同步數據。

建議中大廠,或能穩定運維 redis 和具有高可用方案+失敗重試方案的廠子使用;小廠慎用吧。

資料庫自增模式

在早年,確實看到過依靠資料庫自增的方式,進行唯一 ID 的生成。此種方式主要是依賴資料庫的自增來實現;想要一個唯一的 ID,那麼往帶有自增主鍵的表裡插入一條資料吧,這樣 sql 回傳的時候,就會把自增 ID 回傳來了

優點: 資料庫自增產生的 ID能滿足唯一性也基本上能滿足自增性

缺點:
生成速度較慢。受限於資料庫的實作方式,,ID 產生速度相較於前兩者至少慢一個數量級,畢竟在產生的時候資料庫有磁碟 IO 操作;高並發下,不建議使用此種方式。而且每次要取得一個 ID,還需要先往資料庫插入一筆記錄。本來高並發下,資料庫操作就是個瓶頸了,用了此種方式還加劇了資料庫的負擔。

資料庫號段模式

資料庫號段模式,可以看做是資料庫自增模式的升級版,在資料庫自增模式上進行了效能最佳化,解決了 ID 生成慢的問題。 思路入下圖:

img

資料庫自增模式,每次取得一個 ID,都需要操作一次庫,太慢了。號段模式每次操作資料庫是申請一個號段的範圍。

例如操作一次資料庫,申請 1000 到 2000 是這個應用的業務申請的號段;然後只能這個應用實例的業務能用;應用申請了號段後放到內存中,每次業務需要的時候,從內存裡累加 1 返回,在 java 裡也有現成的工具,比如 AtomicLong 包裝下(省事還能解決並發問題);如果發現快不夠了,還能異步提前從數據庫中申請,放入內存中,這樣就避免了業務需要唯一 ID 的時候,再去資料庫申請,加快業務取得 ID 的速度。

優點:
能滿足唯一性,也能滿足自增性,效能也不差,美團開源的 leaf 使用此種模式,速度能到 5W/秒。

缺點:
對資料庫是強依賴;但我想大多數業務系統基本上都依賴資料庫吧

個人認為此方案最適合小廠;性能不差,也不需要依賴太多的中間件;而且大廠裡也不缺乏使用此方案身影,例如滴滴開源的 Tinyid 也是用的此模式

雪花演算法模式

雪花演算法(Snowflake)出自大名鼎鼎的 twitter 公司之手。該演算法開源後,深受大廠好評。百度基於雪花演算法思想,也開發了自己開源的 UidGenerator 分散式 ID 產生器。

img

Snowflake 產生的是 Long 類型的 ID,我們知道一個 Long 類型佔 8 個位元組空間,1 個位元組又佔 8 位元,也就是說一個 Long 類型會用 64 個位元。

Snowflake ID 組成結構:正數位元(佔 1 位元)+ 時間戳記(佔 41 位元)+ 機器 ID(佔 5 位元)+ 資料中心(佔 5 位元)+ 自增值(佔 12 位元),總共 64 位元。

  • 第一個 bit 位(1bit):Java 中 long 的最高位是符號位代表正負,正數是 0,負數是 1,一般生成 ID 都為正數,所以預設為 0。
  • 時間戳部分(41bit):毫秒級的時間,不建議存當前時間戳,而是用(當前時間戳- 固定開始時間戳)的差值,可以使產生的 ID 從更小的值開始;41 位的時間戳可以使用 69 年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69 年(如果你建造的 IT 系統要使用 100 年,慎用此法)
  • 工作機器 id(10bit):也叫做 workId,這個可以靈活配置,機房或機器號碼組合都可以。
  • 序號部分(12bit),自加值支援同一毫秒內同一個節點可以產生 4096 個 ID

優點:

  • 雪花演算法產生的 ID,確保唯一性;
  • 隨這時間齒輪的流轉,也能滿足自增性;有大廠的背書,生成速度也是超快;
  • 對第三方中間件也是弱依賴

缺點:

  • 存在時鐘回撥問題,所有機器時鐘必須保存同步,否則會導致生成重複 ID
  • 產生的數字會隨這時間的流逝增加,不會因為資料量的增加而增加,在某些業務場景較不適用
  • 需要維護機器 ID 的配置;或依賴第三方中間件根據機器信息,產生機器 ID

這麼多的分佈式唯一 id 生成方法,我該選哪一種?

為了方便選擇,列了個表。到底我的應用程式裡,該選哪個方案了?還需結合自身業務,團隊大小,技術實力,實現複雜度和維護難易度進行選擇。

img

想偷懶,有現成的唯一 ID 產生工具嗎?

有的,像是美團開源的 leaf 能支援資料庫號段和雪花模式

百度的 UidGenerator 使用的是雪花模式

而滴滴的 Tinyid 使用的是號段模式,可以認為是基於美團 leaf 的開源版

Reference