在分布式系統(tǒng)中,通常會(huì)用到分布式ID來標(biāo)注數(shù)據(jù)的唯一性,而分布式ID的生成方式又多種多樣,今天我們就來討論一下主流的分布式ID生成策略。
分布式ID基本需求
- 全局唯一
- 趨勢遞增
- 信息安全
全局唯一
這是基本要求,不必解釋
趨勢遞增
為什么要趨勢遞增呢?
第一,由于我們的分布式ID,是用來標(biāo)識(shí)數(shù)據(jù)唯一性的,所以多數(shù)時(shí)候會(huì)被定義為主鍵或者唯一索引。
第二,并且絕大多數(shù)互聯(lián)網(wǎng)公司使用的數(shù)據(jù)庫是:MySQL,存儲(chǔ)引擎為innoDB。
對于 B+Tree這個(gè)數(shù)據(jù)結(jié)構(gòu)來講,數(shù)據(jù)以自增順序來寫入的話,b+tree的結(jié)構(gòu)不會(huì)時(shí)常被打亂重塑,存取效率是最高的。
信息安全
由于數(shù)據(jù)是遞增的,所以,惡意用戶的可以根據(jù)當(dāng)前ID推測出下一個(gè),非常危險(xiǎn),所以,我們的分布式ID盡量做到不易被破解。
數(shù)據(jù)庫主鍵自增(Flicker)
基于數(shù)據(jù)庫主鍵自增的方案,名為 Flicker。
主要是利用MySQL的自增主鍵來實(shí)現(xiàn)分布式ID。
以下為 Flicker實(shí)現(xiàn)分布式ID的主流做法:
1、需要單獨(dú)建立一個(gè)數(shù)據(jù)庫實(shí)例:flicker
create database `flicker`;
2、創(chuàng)建一張表:sequence_id
create table sequence_id( id bigint(20) unsigned NOT NULL auto_increment, stub char(10) NOT NULL default '', PRIMARY KEY (id), UNIQUE KEY stub (stub) ) ENGINE=MyISAM;
為什么用 MyISAM?不用 InnoDB?個(gè)人推測原因是: flicker算法出來的時(shí)候,MySQL的默認(rèn)引擎還依舊是 MyISAM而不是 InnoDB,作者只是想用默認(rèn)引擎而已,并無其他原因。
- stub: 票根,對應(yīng)需要生成 Id 的業(yè)務(wù)方編碼,可以是項(xiàng)目名、表名甚至是服務(wù)器 IP 地址。
- stub 要設(shè)置為唯一索引
3、使用以下SQL來獲取ID
REPLACE INTO ticket_center (stub) VALUES ('test'); SELECT LAST_INSERT_ID();
Replaceinto 先嘗試插入數(shù)據(jù)到表中,如果發(fā)現(xiàn)表中已經(jīng)有此行數(shù)據(jù)(根據(jù)主鍵或者唯一索引判斷)則先刪除此行數(shù)據(jù),然后插入新的數(shù)據(jù), 否則直接插入新數(shù)據(jù)。
一般 stub為特殊的相同的值。
這樣,一個(gè)分布式ID系統(tǒng)算是可以搭建運(yùn)行了。但是,有人要問:“這是一個(gè)單實(shí)例、單點(diǎn)的系統(tǒng),萬一掛了,豈不是影響所有關(guān)聯(lián)的業(yè)務(wù)方?”
改進(jìn)升華
是的。確實(shí)如此,因此又有人說:“可以利用MySQL主從模式,主庫掛了,使用從庫。”
這只能算是一種比較low的策略,因?yàn)槿绻鲙鞉炝耍瑥膸鞗]來得及同步,就會(huì)生成重復(fù)的ID。
有沒有更好的方法呢?
我們可以使用“雙主模式“,也就是有兩個(gè)MySQL實(shí)例,這兩個(gè)都能生成ID。
如圖所示,我們原來的模式:
雙主模式是該怎么樣呢?如何保持唯一性?
我們可以讓一臺(tái)實(shí)例生成奇數(shù)ID,另一臺(tái)生成偶數(shù)ID。
奇數(shù)那一臺(tái):
set auto_increment_offset=1;--起始值 set auto_increment_increment=2;--步長
偶數(shù)那一臺(tái):
set auto_increment_offset=2;--起始值 set auto_increment_increment=2;--步長
當(dāng)兩臺(tái)都OK的時(shí)候,隨機(jī)取其中的一臺(tái)生成ID;若其中一臺(tái)掛了,則取另外一臺(tái)生成ID。
如圖所示:
細(xì)心會(huì)發(fā)現(xiàn),N個(gè)節(jié)點(diǎn),只要起始值為1,2,...N,然后步長為N,就會(huì)生成各不相同的ID。(PS:后文有推導(dǎo)公式)
總結(jié)
優(yōu)點(diǎn):
- 簡單。充分利用了數(shù)據(jù)庫自增 ID 機(jī)制,生成的 ID 有序遞增。
- ID遞增
缺點(diǎn):
- 并發(fā)量不大。
- 水平擴(kuò)展困難,系統(tǒng)定義好了起始值、步長和機(jī)器臺(tái)數(shù),跑起來之后,添加額外的機(jī)器困難。
- 安全系數(shù)低
redis
Redis為單線程的,所以操作為原子操作,利用 incrby命令可以生成唯一的遞增ID。
原理
單機(jī)單點(diǎn),吞吐不夠,加集群
假設(shè)N個(gè)節(jié)點(diǎn),則步長為N,節(jié)點(diǎn)起始值為1,2,…… N。則三個(gè)節(jié)點(diǎn)生成的ID一定不同!
想想為什么?
以上信息條件可以轉(zhuǎn)化為數(shù)學(xué)推理: 1+x*N=2+y*N且x、y、N都為整成數(shù)且N不為1,試問等式存不存在?
答: 假設(shè)存在在起始值是1的節(jié)點(diǎn)上疊加x次之后等于起始值為2、疊加y次的值, 既 “1 + x * N = 2 + y * N” 等式成立 則: x * N = 1 + y * N x * N - y * N = 1 (x - y) * N = 1 (x - y) = 1 / N 又因?yàn)?x、y都為整成數(shù); 所以x - y 必為整成數(shù); 又因?yàn)橹挥蠳等于1的時(shí)候,1/N才為整成數(shù); 與條件N為1不符合,所以不存在。
同理可證 1+x*N=3+y*N和 2+x*N=3+y*N也是如此。
優(yōu)點(diǎn)
- 性能顯然高于基于數(shù)據(jù)庫的 Flicker方案
- ID遞增
缺點(diǎn)
- 水平擴(kuò)展困難
- Redis集群宕機(jī)可能會(huì)產(chǎn)生重復(fù)的id
- 易破解
UUID
想必這個(gè)大家都熟悉。 UUID是通用唯一識(shí)別碼(Universally Unique Identifier)的縮寫,是一種軟件建構(gòu)的標(biāo)準(zhǔn),亦為開放軟件基金會(huì)組織在分布式計(jì)算環(huán)境領(lǐng)域的一部分。
原理
UUID是由一組32位數(shù)的16進(jìn)制數(shù)字所構(gòu)成,是故UUID理論上的總數(shù)為16^32 = 2^128,約等于3.4 x 10^38。也就是說若每納秒產(chǎn)生1兆個(gè)UUID,要花100億年才會(huì)將所有UUID用完。
UUID是利用同一時(shí)空中的所有機(jī)器都是唯一的這一規(guī)則來確保唯一性的。
具體外形為:
通常由以下幾部分組成:
- 系統(tǒng)時(shí)間
- 時(shí)鐘序列
- 全局唯一的IEEE機(jī)器識(shí)別,如網(wǎng)卡mac、機(jī)器SN等
生成方式多種多樣,業(yè)界公認(rèn)的是五種,分別是uuid1,uuid2,uuid3,uuid4,uuid5。
目前使用最廣泛的UUID是微軟的 GUID。
優(yōu)點(diǎn)
- 本地生成,性能極佳。無網(wǎng)絡(luò)消耗
- 全局唯一
缺點(diǎn)
- 存儲(chǔ)麻煩。16字節(jié)128位,通常以36長度的字符串表示,很多場景不適用
- 通常是字符串,非自增,無序,不利于做主鍵。每次插入都會(huì)對B+tree結(jié)構(gòu)進(jìn)行修改
- 破解相對困難,但是也不安全。參考"梅麗莎病毒事件,病毒作者制作的UUID包含Mac地址,被警方破解后,直接定位,抓捕歸案"
snowflake
snowflake即雪花算法,Twitter發(fā)明的。
原理
外形長這樣:
- 1位不用。二進(jìn)制中最高位為1的都是負(fù)數(shù),但是我們生成的id一般都使用整數(shù),所以這個(gè)最高位固定是0。
- 41位,用來記錄毫秒的時(shí)間戳。41位可以表示的數(shù)值范圍是:0 至 2^{41}-1,減1是因?yàn)榭杀硎镜臄?shù)值范圍是從0開始算的,而不是1,轉(zhuǎn)化為年則是 2^{41}-1)/(1000*60*60*24*365)=69年。
- 10位,用來記錄工作機(jī)器id。最多可以部署在2^{10} = 1024個(gè)節(jié)點(diǎn),我們可以根據(jù)具體的業(yè)務(wù)來定制具體分配的機(jī)器數(shù)量和每臺(tái)機(jī)器1毫秒產(chǎn)生的id序號(hào)number數(shù)。例如可以把10bit分5bit給IDC,分5bit給工作機(jī)器。這樣就可以表示32個(gè)IDC,每個(gè)IDC下可以有32臺(tái)機(jī)器,可以將內(nèi)容配置在配置文件中,服務(wù)去獲取。
- 12位。用來表示單臺(tái)機(jī)器每毫秒生成的id序號(hào),12位bit可以表示的最大正整數(shù)為2^12 - 1 = 4096,若超過4096,則重新從0開始。即,每臺(tái)機(jī)器1毫秒內(nèi)最多產(chǎn)生4096個(gè)ID,足夠用了。
最后將上述4段bit通過位運(yùn)算拼接起來組成64位bit.
由于是64位bit,所以完全可以用數(shù)字來表示ID。
基本是根據(jù):
優(yōu)點(diǎn)
- ID為數(shù)字且時(shí)間位在高位,整個(gè)ID都是趨勢遞增的。
- 不依賴任何第三方庫,完全可以自己寫,且性能非常高。
- 可根據(jù)業(yè)務(wù)定制分配bit位,非常靈活。得益于 10位機(jī)器IDbit位。
- 不太容易破解
缺點(diǎn)
- 依賴機(jī)器的時(shí)間,如果機(jī)器時(shí)間不準(zhǔn)或者回?fù)埽赡軐?dǎo)致重復(fù)
總結(jié)
在國內(nèi)也得到了比較普遍的應(yīng)用,各大廠根據(jù)其基本原理,生成了自己的規(guī)則:
- 百度的uid-generator:https://github.com/baidu/uid-generator
- 美團(tuán)Leaf:https://github.com/zhuzhong/idleaf
請關(guān)注我的微信公眾號(hào) 互聯(lián)網(wǎng)技術(shù)窩,問題直接在公眾號(hào)內(nèi)留言即可
參考文獻(xiàn):
[flicker算法原文] http://code.flickr.com/blog/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/
[分布式唯一ID極簡教程] https://mp.weixin.qq.com/s/cqIK5Bv1U0mT97C7EOxmnA
[分布式 ID 生成策略] https://mp.weixin.qq.com/s/UAvSUDFJ8Fr0a-Na2Vr22g
[分布式ID系列(2)——UUID適合做分布式ID嗎] https://mp.weixin.qq.com/s/kZAnYzJj4aBrtsk8Q9wA
https://segmentfault.com/a/1190000011282426
https://juejin.im/post/5d6fc8eff265da03ef7a324b#comment
https://segmentfault.com/a/1190000010978305
[Leaf——美團(tuán)點(diǎn)評分布式ID生成系統(tǒng)] https://tech.meituan.com/2017/04/21/mt-leaf.html
[UUID的含義及實(shí)現(xiàn)原理]https://blog.csdn.net/reggergdsg/article/details/92091404
[通用唯一標(biāo)識(shí)碼UUID的介紹及使用] https://mp.weixin.qq.com/s/BjCL076USuhLj9GjhXDaTA
[UUID簡史] https://www.infoq.cn/article/talk-about-the-history-of-uuid/?utmsource=tuicool&utmmedium=referral