為什么需要全局唯一id
在分布式架構(gòu)下,經(jīng)常有需求需要生成全局唯一id,比如優(yōu)惠券等券碼,或者分庫分表,每個表都用自增id,會導致每個表的id都不唯一,都需要生成全局唯一id。
這里介紹幾種分布式架構(gòu)下全局唯一id的生成方式,然后再結(jié)合具體場景說下實際項目中遇到哪些問題和解決方案,最后介紹下美團的全局唯一id生成服務(wù)leaf的實現(xiàn)。
常用解決方案
一、UUID
這是最容易想到的方案,UUID(Universally Unique Identifier)是32個16進制數(shù)字,以連字號分為五段,形式為8-4-4-4-12的36個字符,目前生成方式有5種,可參考A Universally Unique IDentifier (UUID) URN Namespace
好處就是生成很簡單,每個系統(tǒng)本地生成就可以。
壞處更加明顯,就是太長和無序,會導致以下問題:
1、很多業(yè)務(wù)場景不適用,比如優(yōu)惠券的發(fā)放,可能12位的券池就能滿足發(fā)放的需要了,太長不但不易于存儲,還會導致無法用于券碼的展示
2、不利于做索引。mysql存儲時,uuid的長度太長不利于做主鍵,而且無序性會導致作為主鍵插入時數(shù)據(jù)位置頻繁變動,影響性能
所以,很多場景或者用于主鍵的情況下,都不能使用uuid
二、數(shù)據(jù)庫自增主鍵
可以創(chuàng)建一個表,通過數(shù)據(jù)插入獲取對應(yīng)的自增主鍵,作為全局唯一id
缺點也很明顯,就是高并發(fā)的場景下,受限于單臺mysql的性能。而且可用性差,DB出現(xiàn)問題會導致id無法生成。
當然可以通過主從的方式增強可用性,同時增加表采用不同自增步長的方式增加并發(fā)性能,比如假設(shè)我們要部署N臺機器,步長需設(shè)置為N,每臺的初始值依次為0,1,2…N-1;但是還會帶來新的問題,比如不利于拓展,想提升性能就要堆機器,但是步長已經(jīng)確定不好更改,主從延遲會導致唯一id的不唯一,。
三、snowflake
這是twitter開源的分布式id生成算法,這種方案把64-bit分別劃分成多段,分開來標示機器、時間等,比如在snowflake中的64-bit分別表示如下圖(圖片來自網(wǎng)絡(luò))所示:

上面第一個部分,是1個bit:0,這個是無意義的上面第二個部分是41個bit:表示的是時間戳;上面第三個部分是5個bit:表示的是機房id,10001上面第四個部分是5個bit:表示的是機器id,1 1001上面第五個部分是12個bit:表示的序號,就是某個機房某臺機器上這一毫秒內(nèi)同時生成的id的序號,0000 00000000
優(yōu)點就是靈活,5位可以用來做業(yè)務(wù)標識
缺點呢就是比較依賴時鐘
實際應(yīng)用
場景一:分庫分表后數(shù)據(jù)唯一id
需求:
- id要作為主鍵,即要滿足趨勢遞增
- id長度盡量小
這種場景我們在項目中使用了數(shù)據(jù)庫自增主鍵的方式,創(chuàng)建了32張表,步長32并設(shè)置遞增初始值的方式,生成唯一id,可以較好的滿足該場景。
場景二:團購券券碼
需求:
- 券碼唯一且不能趨勢遞增,不然用戶會猜測券碼,不安全
- 券碼長度控制在可接受范圍
這種場景項目中用了碰撞的方式,隨機生成的固定長度券碼保存在券碼表,券碼唯一索引,job定時補充券碼表并撈取可用券碼到緩存券池。