聊聊數(shù)據(jù)庫主鍵ID生成的那些坑

相信我們一談到數(shù)據(jù)存入數(shù)據(jù)庫時猎塞,我們都會為數(shù)據(jù)庫的表設置一個表主鍵(PK)试读,作為表中每條記錄的唯一標識,這也是數(shù)據(jù)庫設計范式中的第一范式邢享。那么鹏往,自打我們使用數(shù)據(jù)庫來存儲數(shù)據(jù)時,數(shù)據(jù)庫的廠商都會為我們提供自動生成主鍵ID值的功能骇塘。例如伊履,我們熟悉的Mysql是通過主鍵自增的方式來生成,Oracle則是通過定義序列來為主鍵ID賦值款违,SqlServer與Mysql一樣也提供了主鍵自增的方式唐瀑。
那么,有沒有想過數(shù)據(jù)庫的這種主鍵生成策略有沒有問題呢插爹?問題肯定是有的哄辣,比如请梢,當我們的應用產(chǎn)生的數(shù)據(jù)越來越龐大時,業(yè)界都會采用水平拆分的方式力穗,也就是我們常說的分庫分表策略來對數(shù)據(jù)進行拆分存儲毅弧。一旦數(shù)據(jù)庫做了分庫分表,那么問題就來了当窗。舉個例子够坐,我們有一張用戶表(user_tb),然后我們現(xiàn)在把user_tb做了拆分崖面,分成了5張用戶表來存(user_tb1,user_tb2,user_tb3,user_tb4,user_5)元咙,假設原先user_tb表里有10條記錄,id值為1~10,那么分完表后巫员,數(shù)據(jù)按照id值取模的方式存放的話庶香,我們則得到下面5張表:


image.png

當有新用戶注冊的時候,我們需要保存用戶數(shù)據(jù)简识,那么這個時候赶掖,假設是寫到user_tb1,那么由于主鍵是自增的财异,這個時候就都在user_tb1中寫入一條ID值為7的用戶記錄倘零,顯然這條記錄與user_tb2中的記錄ID值重復了。那么戳寸,針對這個問題該如何解決呢?我們可以通過重新設置每張user_tb表的主鍵生成策略來解決拷泽,將5張user_tb表的ID初始值為11疫鹊,然后設置user_tb1的ID自增步長為1,user_tb2的ID自增步長為2司致,以此類推拆吆,user_tb5的步長為5,這樣當有新數(shù)據(jù)保存時脂矫,就可以保存了5張表各自生成的ID都不會與其他表重復了枣耀,這樣也就解決了ID重復的問題。但是庭再,當我們的數(shù)據(jù)進一步增長的時候捞奕,我們發(fā)現(xiàn)5張表已經(jīng)無法保存的時候,就需要繼續(xù)擴展拄轻,增加分表來分灘數(shù)據(jù)颅围,而這個時候,我們又需要去修改每張表的主鍵生成策略恨搓,顯然這種方式院促,數(shù)據(jù)遷移的成本是巨大的筏养,也需要DBA長期進行維護。那么常拓,我們也許就會想渐溶,我們不再通過數(shù)據(jù)庫自己生成ID,而是通過我們的應用程序來生成主鍵ID弄抬,是否可以掌猛?
于是,我們會發(fā)現(xiàn)眉睹,通過程序生成主鍵ID這種方法荔茬,我們就需要考慮在高并發(fā)的情況,程序需要保證生成的主鍵ID是全局唯一的竹海,且不可以與歷史生成的ID重復慕蔚。相信很多同學都使用過SnowFlake算法來生成全局主鍵ID,那么你是否對SnowFlake算法有所了解呢斋配?
SnowFlake算法是 Twitter 開源的分布式 id 生成算法孔飒。其核心思想就是:使用一個 64 bit 的 long 型的數(shù)字作為全局唯一 id。在分布式系統(tǒng)中的應用十分廣泛艰争,且ID 引入了時間戳坏瞄,基本上保持自增的。
從下圖我們可以看到甩卓,SnowFlake ID的組成部分鸠匀,它是由1位符號位,41位時間戳逾柿,10位機器ID缀棍,12位序列號組成。我解釋下各個部分的具體含義:

  • 符號位:基本不用机错,該部分值固定為0爬范,可無需理會;
  • 時間戳:用來記錄時間戳弱匪,毫秒級青瀑。41位可以表示2的41次方-1個數(shù)字,如果只用來表示正整數(shù)(計算機中正數(shù)包含0)萧诫,可以表示的數(shù)值范圍是:0 至 2的41次方-1斥难,減1是因為可表示的數(shù)值范圍是從0開始算的,而不是1财搁。也就是說41位可以表示2的41次方-1個毫秒的值蘸炸,轉化成單位年則是(2的41次方-1)/1000606024365=69年。
  • 機器ID:10位用來記錄工作機器ID尖奔,可提供2的10次方搭儒,1024個數(shù)字穷当,包含5位數(shù)據(jù)中心ID+5位工作進程ID。
  • 序列號:12位序列號可提供2的12次方-1淹禾,即4095個數(shù)字馁菜,序列號為同一時間戳下,一毫秒可生成4095個序列號铃岔。


    image.png

    通過上面對SnowFlake ID的分析汪疮,我們可以得到以下結論。SnowFlake算法毁习,在單體應用中智嚷,一毫秒可以為我們產(chǎn)生4095個ID,可保證單體應用持續(xù)69年生成全局唯一的ID纺且。說到這里盏道,我們可能會覺得,一個應用能跑69年基本上已經(jīng)很了不起了载碌,就算69年后ID會有重復的問題猜嘱,相信69年后一定會有更好的ID生成解決方案的出現(xiàn),所以不必擔心嫁艇。那么朗伶,由于我們業(yè)務的不斷壯大,我們的系統(tǒng)會從原來的單體架構變成分布式的系統(tǒng)架構步咪。這時论皆,SnowFlake ID還可以保證我們的ID生成是全局唯一的嗎?答案是不一定能保證歧斟。

  • 進程ID沖突問題
    舉個例子:在分布式架構中纯丸,同一個機房,我們會把應用服務部署在多臺服務器中静袖。那么這個時候,由于應用服務連接的是同一個數(shù)據(jù)中心俊扭,那么不同服務器的應用服務連接的數(shù)據(jù)中心ID是一樣的队橙,而不同服務器的應用服務進程ID是否有可能會出現(xiàn)一樣呢,是有可能的萨惑,那么當系統(tǒng)高并發(fā)時捐康,在同一毫秒下,不同服務器上的應用服務生成的ID是有一定概率發(fā)生重復的庸蔼〗庾埽可能,我們會覺得這種情況發(fā)生的機率很低姐仅,可以通過拋出異常花枫,重新讓用戶再提交刻盐。但是,問題的確還是會一直存在劳翰。
  • 時鐘回播
    舉個例子:在分布式架構中敦锌,我們一般會選擇Linux服務器來部署我們的應用,由于SnowFlake算法生成ID對時間戳的依賴佳簸,一旦發(fā)生時鐘回播乙墙,在SnowFlake算法中,那么就可能會導致歷史時刻生成的ID在未來時間由于時鐘回播原因生均,而發(fā)生ID生成重復听想。我們需要對所有Linux服務器進行時鐘統(tǒng)一,這本身就是很麻煩的事马胧,由于Linux系統(tǒng)的機制汉买,每次系統(tǒng)重啟都會導致時鐘往前回播一點點,也就又得重新同步一次所有服務器的時鐘漓雅。這個時候录别,就需要所有服務器都去連接NTP來獲取時間進行統(tǒng)一,而我們的服務器一般都是內網(wǎng)環(huán)境邻吞,是無法連接公網(wǎng)的NTP服務器组题,所以我們需要在自己的內網(wǎng)部署一臺NTP服務器,而NTP服務器要保證高可用抱冷,我們還得部署多一臺NTP服務器來作為備用節(jié)點使用崔列,這不是一件很麻煩的事嗎?維護成本也很高旺遮。
    以上是通過對SnowFlake算法生成ID的機制赵讯,得到的ID有可能出現(xiàn)重復的原因,那么連SnowFlake算法都不能很好解決全局唯一ID生成的問題耿眉,該如何解決這個問題呢边翼?
    別擔心,由于SnowFlake算法存在的這些缺陷鸣剪,目前業(yè)界已經(jīng)有了自己的解決方案组底,并且也都開源了,下面做一下分享筐骇。
  • 滴滴TinyId: https://github.com/didi/tinyid
  • 百度Uid-generator:https://github.com/baidu/uid-generator
  • 美團Leaf:https://github.com/Meituan-Dianping/Leaf
    關于以上大廠的全局ID生成解決方案分享债鸡,3種方案的區(qū)別簡單講解一下:
  • 滴滴TinyId:采用的是借助數(shù)據(jù)庫表的方式,預分配ID分段铛纬,來提供主鍵生成厌均,并沒有使用SnowFlake算法。
  • 百度Uid-generator:采用SnowFlake算法并對其進行了改良了告唆,采用了未來時間的機制保證解決了時鐘回播的問題棺弊,但這種方式相對縮短了ID生成可使用的年限晶密,少于69年。
  • 美團Leaf:3種解決方案中镊屎,個人覺得做得最好的惹挟,也是對SnowFlake算法的改良,通過Zookeeper來保證機器ID沖突的問題缝驳,并且不依賴數(shù)據(jù)庫连锯,采用中心化服務的方式,來提供全局ID的生成用狱,避免了不同服務器時鐘回播的問題运怖,并提供了集群高可用的解決方案。
    關于詳細的實現(xiàn)機制夏伊,此處不再做進一步講解摇展,感興趣的同學,可以上github查閱溺忧,主頁上都提供了文檔資料咏连。
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鲁森,隨后出現(xiàn)的幾起案子祟滴,更是在濱河造成了極大的恐慌,老刑警劉巖歌溉,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垄懂,死亡現(xiàn)場離奇詭異,居然都是意外死亡痛垛,警方通過查閱死者的電腦和手機草慧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匙头,“玉大人漫谷,你說我怎么就攤上這事□逦觯” “怎么了抖剿?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長识窿。 經(jīng)常有香客問我,道長脑融,這世上最難降的妖魔是什么喻频? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮肘迎,結果婚禮上甥温,老公的妹妹穿的比我還像新娘锻煌。我一直安慰自己,他們只是感情好姻蚓,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布宋梧。 她就那樣靜靜地躺著,像睡著了一般狰挡。 火紅的嫁衣襯著肌膚如雪捂龄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天加叁,我揣著相機與錄音倦沧,去河邊找鬼。 笑死它匕,一個胖子當著我的面吹牛展融,可吹牛的內容都是我干的。 我是一名探鬼主播豫柬,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼告希,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烧给?” 一聲冷哼從身側響起燕偶,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎创夜,沒想到半個月后杭跪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡驰吓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年涧尿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片檬贰。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡姑廉,死狀恐怖,靈堂內的尸體忽然破棺而出翁涤,到底是詐尸還是另有隱情桥言,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布葵礼,位于F島的核電站号阿,受9級特大地震影響,放射性物質發(fā)生泄漏鸳粉。R本人自食惡果不足惜扔涧,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枯夜,春花似錦弯汰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至摔吏,卻和暖如春鸽嫂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舔腾。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工溪胶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人稳诚。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓哗脖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親扳还。 傳聞我的和親對象是個殘疾皇子才避,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內容