為什么要生成分布式ID?
在復(fù)雜分布式系統(tǒng)中,往往需要對(duì)大量的數(shù)據(jù)和消息進(jìn)行唯一標(biāo)識(shí)。例如在游戲中,游戲數(shù)據(jù)日漸增長(zhǎng),對(duì)數(shù)據(jù)分庫(kù)分表后需要有一個(gè)唯一ID來(lái)標(biāo)識(shí)一條數(shù)據(jù)或消息,數(shù)據(jù)庫(kù)的自增ID顯然不能滿足需求,那業(yè)務(wù)系統(tǒng)對(duì)ID號(hào)的要求有哪些呢?
1)全局唯一性:不能出現(xiàn)重復(fù)的ID號(hào),既然是唯一標(biāo)識(shí),這是最基本的要求。
2)趨勢(shì)遞增:在MySQL InnoDB引擎中使用的是聚集索引,由于多數(shù)RDBMS使用B-tree的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)索引數(shù)據(jù),在主鍵的選擇上面我們應(yīng)該盡量使用有序的主鍵保證寫(xiě)入性能。
3)單調(diào)遞增:保證下一個(gè)ID一定大于上一個(gè)ID,例如事務(wù)版本號(hào)、IM增量消息、排序等特殊需求。
4)信息安全:如果ID是連續(xù)的,惡意用戶的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號(hào)就更危險(xiǎn)了,競(jìng)對(duì)可以直接知道我們一天的單量。所以在一些應(yīng)用場(chǎng)景下,會(huì)需要ID無(wú)規(guī)則、不規(guī)則。
1、UUID
使用網(wǎng)卡地址、時(shí)間戳和隨機(jī)數(shù)進(jìn)行生成唯一ID,Java中就自帶生成UUID的方法。
優(yōu)點(diǎn):本地即可生成,不需要網(wǎng)絡(luò)開(kāi)銷
缺點(diǎn):字符串占用內(nèi)存,不自增,對(duì)數(shù)據(jù)庫(kù)索引不友好,MySQL官方推薦不要使用
2、snowflake算法

1位:保留位不用。二進(jìn)制中最高位為1的都是負(fù)數(shù),但是我們生成的id一般都使用整數(shù),所以這個(gè)最高位固定是0
41位:用來(lái)記錄時(shí)間戳(毫秒),41位可以表示2^41?1個(gè)數(shù)字,(2^41?1)/(1000?60?60?24?365)=69年
10位:用來(lái)記錄工作機(jī)器id
12位:序列號(hào),用來(lái)記錄同毫秒內(nèi)產(chǎn)生的不同id
優(yōu)點(diǎn):存在自增趨勢(shì),只占用64位
缺點(diǎn):強(qiáng)依賴機(jī)器時(shí)鐘
3、Flicker公司的解決方案
使用MySQL的auto_increment自增特性來(lái)生成唯一ID。
創(chuàng)建優(yōu)惠券表:
CREATETABLEDiscount (idbigint(20)unsignedNOTNULLauto_increment,stubchar(1)NOTNULLdefault'',PRIMARYKEY(id),UNIQUEKEYstub (stub))ENGINE=InnoDB
獲取ID: 在一個(gè)事務(wù)中執(zhí)行如下sql,replace和insert語(yǔ)句區(qū)別主要是replace在插入數(shù)據(jù)的時(shí)候,如果數(shù)據(jù)存在(通過(guò)主鍵和唯一索引來(lái)查找)則先刪除,然后再進(jìn)行插入。
START TRANSACTION;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
COMMIT;
上面這種方法只在單臺(tái)MySQL上生成ID,從高可用角度考慮,接下來(lái)就要解決單點(diǎn)故障問(wèn)題:可以啟用兩臺(tái)數(shù)據(jù)庫(kù)服務(wù)器來(lái)生成ID,通過(guò)區(qū)分auto_increment的起始值和步長(zhǎng)來(lái)生成奇偶數(shù)的ID。
DiscountServer1// 優(yōu)惠券服務(wù)1
auto-increment-increment =2// 自增值
auto-increment-offset =1// 起始值
DiscountServer2// 優(yōu)惠券服務(wù)2
auto-increment-increment =2// 自增
auto-increment-offset =2// 起始值
優(yōu)點(diǎn):充分借助數(shù)據(jù)庫(kù)的自增ID機(jī)制,提供高可靠性,生成的ID有序。
缺點(diǎn):強(qiáng)依賴數(shù)據(jù)庫(kù),占用兩個(gè)獨(dú)立的MySQL實(shí)例,有些浪費(fèi)資源,成本較高,而且增刪MySQL實(shí)例很復(fù)雜。
4、MongoDB的ObjectId
MongoDB中我們經(jīng)常會(huì)接觸到一個(gè)自動(dòng)生成的字段:”_id”,類型為ObjectId。上面方法中用到了MySQL數(shù)據(jù)庫(kù)時(shí),主鍵都是設(shè)置成自增的。但在分布式環(huán)境下,這種方法就不可行了,會(huì)產(chǎn)生沖突。為此,MongoDB采用了一個(gè)稱之為ObjectId的類型來(lái)做主鍵。ObjectId是一個(gè)12字節(jié)的 BSON 類型字符串。
4字節(jié):UNIX時(shí)間戳3字節(jié):表示運(yùn)行MongoDB的機(jī)器2字節(jié):表示生成此_id的進(jìn)程3字節(jié):由一個(gè)隨機(jī)數(shù)開(kāi)始的計(jì)數(shù)器生成的值
前9個(gè)字節(jié)保證了同一秒不同機(jī)器不同進(jìn)程產(chǎn)生的ObjectId的唯一性。后三個(gè)字節(jié)是一個(gè)自動(dòng)增加的計(jì)數(shù)器(一個(gè)mongod進(jìn)程需要一個(gè)全局的計(jì)數(shù)器),保證同一秒的ObjectId是唯一的。同一秒鐘最多允許每個(gè)進(jìn)程擁有(256^3 = 16777216)個(gè)不同的ObjectId。
優(yōu)點(diǎn):算法實(shí)現(xiàn)思路和snowflake類似,但是相比更消耗空間