一線大廠的分布式唯一 ID 生成方案是什么樣的伟桅?

一敞掘、前言

分布式系統(tǒng)中我們會對一些數(shù)據(jù)量大的業(yè)務(wù)進行分拆,如:用戶表楣铁,訂單表玖雁。因為數(shù)據(jù)量巨大一張表無法承接,就會對其進行分庫分表盖腕。

但一旦涉及到分庫分表茄菊,就會引申出分布式系統(tǒng)中唯一主鍵ID的生成問題疯潭,永不遷移數(shù)據(jù)和避免熱點的文章中要求需要唯一ID的特性:

  • 整個系統(tǒng)ID唯一
  • ID是數(shù)字類型,而且是趨勢遞增的
  • ID簡短面殖,查詢效率快

什么是遞增竖哩? 如:第一次生成的ID為12,下一次生成的ID是13脊僚,再下一次生成的ID是14相叁。這個就是生成ID遞增。

什么是趨勢遞增辽幌? 如:在一段時間內(nèi)增淹,生成的ID是遞增的趨勢。如:再一段時間內(nèi)生成的ID在【0乌企,1000】之間虑润,過段時間生成的ID在【1000,2000】之間加酵。但在【0-1000】區(qū)間內(nèi)的時候拳喻,ID生成有可能第一次是12,第二次是10猪腕,第三次是14冗澈。

那有什么方案呢?往下看陋葡!

二亚亲、分布式ID的幾種生成方案

2.1、UUID

這個方案是小伙伴們第一個能過考慮到的方案

優(yōu)點:

  • 代碼實現(xiàn)簡單腐缤。
  • 本機生成捌归,沒有性能問題
  • 因為是全球唯一的ID,所以遷移數(shù)據(jù)容易

缺點:

  • 每次生成的ID是無序的岭粤,無法保證趨勢遞增
  • UUID的字符串存儲陨溅,查詢效率慢
  • 存儲空間大
  • ID本事無業(yè)務(wù)含義,不可讀

應(yīng)用場景:

  • 類似生成token令牌的場景
  • 不適用一些要求有趨勢遞增的ID場景

此UUID方案是不適用老顧的需求绍在。

2.2门扇、MySQL主鍵自增

這個方案就是利用了MySQL的主鍵自增auto_increment,默認每次ID加1偿渡。

優(yōu)點:

  • 數(shù)字化臼寄,id遞增
  • 查詢效率高
  • 具有一定的業(yè)務(wù)可讀

缺點:

  • 存在單點問題,如果mysql掛了溜宽,就沒法生成iD了
  • 數(shù)據(jù)庫壓力大吉拳,高并發(fā)抗不住

2.3、MySQL多實例主鍵自增

這個方案就是解決mysql的單點問題适揉,在auto_increment基本上面留攒,設(shè)置step步長

圖片

每臺的初始值分別為1,2,3...N煤惩,步長為N(這個案例步長為4)

優(yōu)點:

  • 解決了單點問題

缺點:

  • 一旦把步長定好后,就無法擴容炼邀;而且單個數(shù)據(jù)庫的壓力大魄揉,數(shù)據(jù)庫自身性能無法滿足高并發(fā)

應(yīng)用場景:

  • 數(shù)據(jù)不需要擴容的場景

此方案也不滿足老顧的需求,因為不方便擴容(記住這個方案拭宁,嘿嘿)

2.4洛退、雪花snowflake算法

這個算法網(wǎng)上介紹了很多,老顧這里就不詳細介紹杰标。雪花算法生成64位的二進制正整數(shù)兵怯,然后轉(zhuǎn)換成10進制的數(shù)。64位二進制數(shù)由如下部分組成:

圖片
  • 1位標識符:始終是0
  • 41位時間戳:41位時間截不是存儲當前時間的時間截腔剂,而是存儲時間截的差值(當前時間截 - 開始時間截 )得到的值媒区,這里的的開始時間截,一般是我們的id生成器開始使用的時間掸犬,由我們程序來指定的
  • 10位機器標識碼:可以部署在1024個節(jié)點袜漩,如果機器分機房(IDC)部署,這10位可以由 5位機房ID + 5位機器ID 組成
  • 12位序列:毫秒內(nèi)的計數(shù)登渣,12位的計數(shù)順序號支持每個節(jié)點每毫秒(同一機器噪服,同一時間截)產(chǎn)生4096個ID序號

優(yōu)點:

  • 此方案每秒能夠產(chǎn)生409.6萬個ID毡泻,性能快
  • 時間戳在高位胜茧,自增序列在低位,整個ID是趨勢遞增的仇味,按照時間有序遞增
  • 靈活度高呻顽,可以根據(jù)業(yè)務(wù)需求,調(diào)整bit位的劃分丹墨,滿足不同的需求

缺點:

  • 依賴機器的時鐘廊遍,如果服務(wù)器時鐘回撥,會導(dǎo)致重復(fù)ID生成

在分布式場景中贩挣,服務(wù)器時鐘回撥會經(jīng)常遇到喉前,一般存在10ms之間的回撥;小伙伴們就說這點10ms王财,很短可以不考慮吧卵迂。但此算法就是建立在毫秒級別的生成方案,一旦回撥绒净,就很有可能存在重復(fù)ID见咒。

此方案暫不符合老顧的需求(嘿嘿,看看怎么優(yōu)化這個方案挂疆,小伙伴們先記赘睦馈)

2.5下翎、Redis生成方案

利用redis的incr原子性操作自增,一般算法為:

年份 + 當天距當年第多少天 + 天數(shù) + 小時 + redis自增

優(yōu)點:

  • 有序遞增宝当,可讀性強

缺點:

  • 占用帶寬视事,每次要向redis進行請求

整體測試了這個性能如下:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">需求:同時10萬個請求獲取ID1、并發(fā)執(zhí)行完耗時:9s左右 2今妄、單任務(wù)平均耗時:74ms 3郑口、單線程最小耗時:不到1ms 4、單線程最大耗時:4.1s </pre>

性能還可以盾鳞,如果對性能要求不是太高的話犬性,這個方案基本符合老顧的要求。

但不完全符合業(yè)務(wù)老顧希望id從 1 開始趨勢遞增腾仅。(當然算法可以調(diào)整為 就一個 redis自增乒裆,不需要什么年份,多少天等)推励。

2.6鹤耍、小結(jié)

以上介紹了常見的幾種分布式ID生成方案。一線大廠的分布式ID方案絕沒有這個簡單验辞,他們對高并發(fā)稿黄,高可用的要求很高。

如Redis方案中跌造,每次都要去Redis去請求杆怕,有網(wǎng)絡(luò)請求耗時,并發(fā)強依賴了Redis壳贪。這個設(shè)計是有風(fēng)險的陵珍,一旦Redis掛了,整個系統(tǒng)不可用违施。

而且一線大廠也會考慮到ID安全性的問題互纯,如:Redis方案中,用戶是可以預(yù)測下一個ID號是多少磕蒲,因為算法是遞增的留潦。

這樣的話競爭對手第一天中午12點下個訂單,就可以看到平臺的訂單ID是多少辣往,第二天中午12點再下一單兔院,又平臺訂單ID到多少。這樣就可以猜到平臺1天能產(chǎn)生多少訂單了排吴,這個是絕對不允許的秆乳,公司絕密啊。

三、一線大廠是如何設(shè)計的呢?

一線大廠的設(shè)計思路其實和小伙伴們思路差不多屹堰,只是多想了1~2層肛冶,設(shè)計上面多了1~2個環(huán)節(jié)。

3.1扯键、改造數(shù)據(jù)庫主鍵自增

上述我們介紹了利用數(shù)據(jù)庫的自增主鍵的特性睦袖,可以實現(xiàn)分布式ID;這個ID比較簡短明了荣刑,適合做userId馅笙,正好符合如何永不遷移數(shù)據(jù)和避免熱點? 根據(jù)服務(wù)器指標分配數(shù)據(jù)量(揭秘篇)文章中的ID的需求。但這個方案有嚴重的問題:

  • 一旦步長定下來厉亏,不容易擴容
  • 數(shù)據(jù)庫壓力山大

小伙伴們看看怎么優(yōu)化這個方案董习。先看數(shù)據(jù)庫壓力大,為什么壓力大爱只?是因為我們每次獲取ID的時候皿淋,都要去數(shù)據(jù)庫請求一次。那我們可以不可以不要每次去忍袷浴窝趣?

思路我們可以請求數(shù)據(jù)庫得到ID的時候,可設(shè)計成獲得的ID是一個ID區(qū)間段训柴。

圖片

我們看上圖哑舒,有張ID規(guī)則表:

1、id表示為主鍵幻馁,無業(yè)務(wù)含義洗鸵。

2、biz_tag為了表示業(yè)務(wù)宣赔,因為整體系統(tǒng)中會有很多業(yè)務(wù)需要生成ID预麸,這樣可以共用一張表維護

3瞪浸、max_id表示現(xiàn)在整體系統(tǒng)中已經(jīng)分配的最大ID

4儒将、desc描述

5、update_time表示每次取的ID時間

我們再來看看整體流程:

1对蒲、【用戶服務(wù)】在注冊一個用戶時钩蚊,需要一個用戶ID;會請求【生成ID服務(wù)(是獨立的應(yīng)用)】的接口

2蹈矮、【生成ID服務(wù)】會去查詢數(shù)據(jù)庫砰逻,找到user_tag的id,現(xiàn)在的max_id為0泛鸟,step=1000

3蝠咆、【生成ID服務(wù)】把max_id和step返回給【用戶服務(wù)】;并且把max_id更新為max_id = max_id + step,即更新為1000

4刚操、【用戶服務(wù)】獲得max_id=0闸翅,step=1000;

5菊霜、 這個用戶服務(wù)可以用ID=【max_id + 1坚冀,max_id+step】區(qū)間的ID,即為【1鉴逞,1000】

6记某、【用戶服務(wù)】會把這個區(qū)間保存到j(luò)vm中

7、【用戶服務(wù)】需要用到ID的時候构捡,在區(qū)間【1液南,1000】中依次獲取id,可采用AtomicLong中的getAndIncrement方法勾徽。

8贺拣、如果把區(qū)間的值用完了,再去請求【生產(chǎn)ID服務(wù)】接口捂蕴,獲取到max_id為1000譬涡,即可以用【max_id + 1,max_id+step】區(qū)間的ID啥辨,即為【1001涡匀,2000】

這個方案就非常完美的解決了數(shù)據(jù)庫自增的問題,而且可以自行定義max_id的起點溉知,和step步長陨瘩,非常方便擴容。

而且也解決了數(shù)據(jù)庫壓力的問題级乍,因為在一段區(qū)間內(nèi)舌劳,是在jvm內(nèi)存中獲取的,而不需要每次請求數(shù)據(jù)庫玫荣。即使數(shù)據(jù)庫宕機了甚淡,系統(tǒng)也不受影響,ID還能維持一段時間捅厂。

3.2贯卦、競爭問題

以上方案中,如果是多個用戶服務(wù)焙贷,同時獲取ID撵割,同時去請求【ID服務(wù)】,在獲取max_id的時候會存在并發(fā)問題辙芍。

如用戶服務(wù)A啡彬,取到的max_id=1000 ;用戶服務(wù)B取到的也是max_id=1000羹与,那就出現(xiàn)了問題,Id重復(fù)了庶灿。那怎么解決注簿?

其實方案很多,加分布式鎖跳仿,保證同一時刻只有一個用戶服務(wù)獲取max_id诡渴。當然也可以用數(shù)據(jù)庫自身的鎖去解決。

圖片

利用事務(wù)方式加行鎖菲语,上面的語句妄辩,在沒有執(zhí)行完之前,是不允許第二個用戶服務(wù)請求過來的山上,第二個請求只能阻塞眼耀。

3.3、突發(fā)阻塞問題

圖片

上圖中佩憾,多個用戶服務(wù)獲取到了各自的ID區(qū)間哮伟,在高并發(fā)場景下,ID用的很快妄帘,如果3個用戶服務(wù)在某一時刻都用完了楞黄,同時去請求【ID服務(wù)】。因為上面提到的競爭問題,所有只有一個用戶服務(wù)去操作數(shù)據(jù)庫,其他二個會被阻塞闷袒。

小伙伴就會問,有這么巧嗎碎税?同時ID用完。我們這里舉的是3個用戶服務(wù)馏锡,感覺概率不大雷蹂;如果是100個用戶服務(wù)呢?概率是不是一下子大了杯道。

出現(xiàn)的現(xiàn)象就是一會兒突然系統(tǒng)耗時變長匪煌,一會兒好了,就是這個原因?qū)е碌慕侗趺慈ソ鉀Q虐杯?

3.4玛歌、雙buffer方案

在一般的系統(tǒng)設(shè)計中昧港,雙buffer會經(jīng)常看到支子,怎么去解決上面的問題也可以采用雙buffer方案创肥。

圖片

在設(shè)計的時候,采用雙buffer方案,上圖的流程:

1叹侄、當前獲取ID在buffer1中巩搏,每次獲取ID在buffer1中獲取

2、當buffer1中的Id已經(jīng)使用到了100趾代,也就是達到區(qū)間的10%

3贯底、達到了10%,先判斷buffer2中有沒有去獲取過撒强,如果沒有就立即發(fā)起請求獲取ID線程禽捆,此線程把獲取到的ID,設(shè)置到buffer2中飘哨。

4胚想、如果buffer1用完了,會自動切換到buffer2

5芽隆、buffer2用到10%了浊服,也會啟動線程再次獲取,設(shè)置到buffer1中

6胚吁、依次往返

雙buffer的方案牙躺,小伙伴們有沒有感覺很酷,這樣就達到了業(yè)務(wù)場景用的ID腕扶,都是在jvm內(nèi)存中獲得的述呐,從此不需要到數(shù)據(jù)庫中獲取了。允許數(shù)據(jù)庫宕機時間更長了蕉毯。

因為會有一個線程乓搬,會觀察什么時候去自動獲取。兩個buffer之間自行切換使用代虾。就解決了突發(fā)阻塞的問題进肯。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市棉磨,隨后出現(xiàn)的幾起案子江掩,更是在濱河造成了極大的恐慌,老刑警劉巖乘瓤,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件环形,死亡現(xiàn)場離奇詭異,居然都是意外死亡衙傀,警方通過查閱死者的電腦和手機抬吟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來统抬,“玉大人火本,你說我怎么就攤上這事危队。” “怎么了钙畔?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵茫陆,是天一觀的道長。 經(jīng)常有香客問我擎析,道長簿盅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任揍魂,我火速辦了婚禮挪鹏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘愉烙。我一直安慰自己讨盒,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布步责。 她就那樣靜靜地躺著返顺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蔓肯。 梳的紋絲不亂的頭發(fā)上遂鹊,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音蔗包,去河邊找鬼秉扑。 笑死,一個胖子當著我的面吹牛调限,可吹牛的內(nèi)容都是我干的舟陆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼耻矮,長吁一口氣:“原來是場噩夢啊……” “哼秦躯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起裆装,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤踱承,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哨免,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茎活,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年琢唾,在試婚紗的時候發(fā)現(xiàn)自己被綠了载荔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡慧耍,死狀恐怖身辨,靈堂內(nèi)的尸體忽然破棺而出丐谋,到底是詐尸還是另有隱情芍碧,我是刑警寧澤煌珊,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站泌豆,受9級特大地震影響定庵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜踪危,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一蔬浙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贞远,春花似錦畴博、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至袱结,卻和暖如春亮隙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背垢夹。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工溢吻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人果元。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓促王,卻偏偏與公主長得像,于是被迫代替她去往敵國和親而晒。 傳聞我的和親對象是個殘疾皇子硼砰,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容