1烤宙、ID生成的要求
- 全局唯一性: 不重復(fù)
- 趨勢(shì)遞增: 多數(shù)的RDBMS數(shù)據(jù)庫(kù)使用B-Tree來(lái)存儲(chǔ)索引結(jié)構(gòu),主鍵有序有利于插入效率 避免緩存失效躺枕,頁(yè)裂變等
- 單調(diào)遞增:保證下一個(gè)ID一定大余上一個(gè)ID,滿足如事務(wù)版本號(hào)蔓姚,消息順序等需求
- 信息安全:避免通過(guò)ID泄露太多信息 如找一天的開頭和結(jié)尾的Id號(hào)差慨丐,可以獲取一個(gè)系統(tǒng)一天的訂單量
2、UUID
36位 8-4-4-4-12 去掉- 32位 byte
優(yōu)點(diǎn):
性能高备闲,
無(wú)需網(wǎng)絡(luò)
缺點(diǎn):
無(wú)序捅暴,
太長(zhǎng),
包含mac地址信息不安全泻骤,
作為DB主鍵不合適 - a、主鍵長(zhǎng)度推薦短點(diǎn) b狱掂、uuid無(wú)序性,數(shù)據(jù)位置頻繁變動(dòng)鸟顺,影響性能
3器虾、類雪花算法 snowflake
64位 bit
1位 不用
41位 時(shí)間戳
10位 workID
12位 序列遞增
可根據(jù)實(shí)際情況調(diào)整 各個(gè)部分占位數(shù)
優(yōu)點(diǎn):
1、趨勢(shì)遞增 時(shí)間位在高位
2欧芽、不依賴第三方系統(tǒng)
3挤悉、可根據(jù)需求分配bit位
缺點(diǎn):
1、時(shí)鐘回?fù)艿那闆r下昏鹃,可能出現(xiàn)重復(fù)id --- 可以等待一段時(shí)間诀诊,時(shí)鐘跟上次正向后再發(fā)號(hào)
2、機(jī)器id的分配方案属瓣,機(jī)器宕機(jī)后抡蛙,機(jī)器id回收的問(wèn)題 --- 使用zk做id分配
3、機(jī)器id存在上限 10bit 1024 ---
算法應(yīng)用:如MongoDB的ObjectId自生成
時(shí)間+機(jī)器碼+pid+inc 4+3+2+3
共12個(gè)byte 最終標(biāo)識(shí)為24ge16進(jìn)制字符
1byte -> 8bit 16進(jìn)制 -> 4bit 所以1byte可以標(biāo)識(shí)為2個(gè)16進(jìn)制字符
571094e2976aeb1df982ad4e
57 -> 01010111
4粗截、數(shù)據(jù)庫(kù)生成
如mysql 利用 auto_increment_increment
auto_increment_offset
sql replace into
如果已存在數(shù)據(jù)熊昌,則刪除后插入 如果不存在數(shù)據(jù)直接插入
begin;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
commit;
事務(wù)保證 插入的和查詢到的是同一條ID記錄
不用insert 是保證數(shù)據(jù)庫(kù)不會(huì)無(wú)限增長(zhǎng)
不用delete 是減少一次查詢暂刘,減少交互
優(yōu)點(diǎn):
1伍派、單調(diào)遞增
2、直接使用數(shù)據(jù)庫(kù)自帶實(shí)現(xiàn)昂利,簡(jiǎn)單
缺點(diǎn):
1铁坎、強(qiáng)依賴DB犁苏,DB宕機(jī)不可用 - 如果使用主從機(jī)解決高可用問(wèn)題傀顾,又會(huì)出現(xiàn)主從切換時(shí)的重復(fù)發(fā)號(hào)問(wèn)題
2碌奉、性能瓶頸依賴單mysql數(shù)據(jù)庫(kù)的性能
可以分庫(kù)分表橫向擴(kuò)容,解決單mysql的性能問(wèn)題赐劣,如步長(zhǎng)相同,每臺(tái)msyql都用不同的初始值魁兼,可以做到不重復(fù)發(fā)號(hào)
問(wèn)題:
ID沒(méi)法單調(diào)遞增了,只能趨勢(shì)遞增
每次生成號(hào)都得讀寫一次數(shù)據(jù)庫(kù)盖呼,壓力還是在數(shù)據(jù)庫(kù)上
定好步長(zhǎng)和初始值后化撕,后期再擴(kuò)容困難
5、Leaf數(shù)據(jù)庫(kù)方案
表增加業(yè)務(wù)字段 biz_tag 用來(lái)做不同業(yè)務(wù)區(qū)分 - 方便后續(xù)分庫(kù)分表基于業(yè)務(wù)字段進(jìn)行拆分
表字段step max_id 自己存儲(chǔ)當(dāng)前發(fā)號(hào)最大id號(hào)和步長(zhǎng)蟹瘾,不依賴數(shù)據(jù)庫(kù)自己的auto_increment特性
這時(shí)的step步長(zhǎng)標(biāo)識(shí)一次取號(hào)的批量數(shù)據(jù)掠手,等這批數(shù)據(jù)用完后再來(lái)取數(shù),減輕了mysql的交互壓力
begin
update table set max_id=max_id+step where biz_tag=xxx;
select biz_tag, max_id, step where biz_tag=xxx;
commit
優(yōu)點(diǎn):
方便橫向擴(kuò)展
ID是long型64位遞增數(shù)字众雷,滿足主鍵要求
客戶端有號(hào)段緩存 max_id-step 到max_id魁衙,取完才去數(shù)據(jù)庫(kù)取 能一定程度緩解可用性
max_id可自定義,方便其他業(yè)務(wù)遷移
缺點(diǎn):
遞增數(shù)據(jù)容易泄露發(fā)號(hào)規(guī)律
當(dāng)號(hào)段用完后剖淀,去數(shù)據(jù)庫(kù)取數(shù)時(shí),還是可能會(huì)引起高并發(fā)翻诉,阻塞
DB宕機(jī)會(huì)不可用
優(yōu)化方案:
1、雙buffer 號(hào)段還沒(méi)用完時(shí)舒岸,就去提前取下一個(gè)號(hào)段芦圾,可以不需要等待取號(hào)阻塞 2個(gè)segment
segment設(shè)置為10分鐘的高峰用號(hào)量, 這樣DB宕機(jī)也有10-20分鐘
不會(huì)阻塞在segment取號(hào)
2个少、DB可用性 使用一主兩從夜焦,分機(jī)房部署,半同步方式同步數(shù)據(jù) 主從切換需要中間件
6茫经、Leaf雪花算法方案
還是1+41+10+12 分配方案
1、利用zk的持久順序節(jié)點(diǎn) 來(lái)生成workid 抹镊,重啟后直接獲取是否已經(jīng)分配過(guò)id
2瞪慧、弱依賴zk,每次的workid獲取后都會(huì)在本地存儲(chǔ)
3氨菇、周期性上傳本機(jī)時(shí)間到zk上妓湘,每次取號(hào)都要檢查當(dāng)前時(shí)間是否出現(xiàn)了回?fù)埽霈F(xiàn)則失敗榜贴,告警
4、檢查本機(jī)時(shí)間和zk上的平均時(shí)間是否偏移過(guò)大鹃共,出現(xiàn)則失敗告警
7驶拱、我們的實(shí)現(xiàn)
redis 單線程原子性 實(shí)現(xiàn)一個(gè)業(yè)務(wù)字段的遞增
private long getUniqueAtomicLong(String tag) {
RAtomicLong atomicLong = redissonClient.getAtomicLong(tag);
boolean flag = atomicLong.compareAndSet(Long.MAX_VALUE, 0);
if(flag) {
return Long.MAX_VALUE;
}
long lo = atomicLong.getAndIncrement();
return lo;
}