ID Generator id生成器 分布式id生成系統(tǒng),簡(jiǎn)單易用、高性能、高可用的id生成系統(tǒng)
簡(jiǎn)介
Tinyid是用Java開發(fā)的一款分布式id生成系統(tǒng),基于數(shù)據(jù)庫號(hào)段算法實(shí)現(xiàn),關(guān)于這個(gè)算法可以參考美團(tuán)leaf或者tinyid原理介紹。Tinyid擴(kuò)展了leaf-segment算法,支持了多db(master),同時(shí)提供了java-client(sdk)使id生成本地化,獲得了更好的性能與可用性。Tinyid在滴滴客服部門使用,均通過tinyid-client方式接入,每天生成億級(jí)別的id。
tinyid系統(tǒng)架構(gòu)圖
下面是一些關(guān)于這個(gè)架構(gòu)圖的說明:
- nextId和getNextSegmentId是tinyid-server對(duì)外提供的兩個(gè)http接口
- nextId是獲取下一個(gè)id,當(dāng)調(diào)用nextId時(shí),會(huì)傳入bizType,每個(gè)bizType的id數(shù)據(jù)是隔離的,生成id會(huì)使用該bizType類型生成的IdGenerator。
- getNextSegmentId是獲取下一個(gè)可用號(hào)段,tinyid-client會(huì)通過此接口來獲取可用號(hào)段
- IdGenerator是id生成的接口
- IdGeneratorFactory是生產(chǎn)具體IdGenerator的工廠,每個(gè)biz_type生成一個(gè)IdGenerator實(shí)例。通過工廠,我們可以隨時(shí)在db中新增biz_type,而不用重啟服務(wù)
- IdGeneratorFactory實(shí)際上有兩個(gè)子類IdGeneratorFactoryServer和IdGeneratorFactoryClient,區(qū)別在于,getNextSegmentId的不同,一個(gè)是DbGet,一個(gè)是HttpGet
- CachedIdGenerator則是具體的id生成器對(duì)象,持有currentSegmentId和nextSegmentId對(duì)象,負(fù)責(zé)nextId的核心流程。nextId最終通過AtomicLong.andAndGet(delta)方法產(chǎn)生。
性能與可用性
性能
- http方式訪問,性能取決于http server的能力,網(wǎng)絡(luò)傳輸速度
- java-client方式,id為本地生成,號(hào)段長度(step)越長,qps越大,如果將號(hào)段設(shè)置足夠大,則qps可達(dá)1000w+
可用性
- 依賴db,當(dāng)db不可用時(shí),因?yàn)閟erver有緩存,所以還可以使用一段時(shí)間,如果配置了多個(gè)db,則只要有1個(gè)db存活,則服務(wù)可用
- 使用tiny-client,只要server有一臺(tái)存活,則理論上可用,server全掛,因?yàn)閏lient有緩存,也可以繼續(xù)使用一段時(shí)間
Tinyid的特性
- 全局唯一的long型id
- 趨勢(shì)遞增的id,即不保證下一個(gè)id一定比上一個(gè)大
- 非連續(xù)性
- 提供http和java client方式接入
- 支持批量獲取id
- 支持生成1,3,5,7,9…序列的id
- 支持多個(gè)db的配置,無單點(diǎn)
適用場(chǎng)景:只關(guān)心id是數(shù)字,趨勢(shì)遞增的系統(tǒng),可以容忍id不連續(xù),有浪費(fèi)的場(chǎng)景 不適用場(chǎng)景:類似訂單id的業(yè)務(wù)(因?yàn)樯傻膇d大部分是連續(xù)的,容易被掃庫、或者測(cè)算出訂單量)
推薦使用方式
tinyid-server推薦部署到多個(gè)機(jī)房的多臺(tái)機(jī)器
多機(jī)房部署可用性更高,http方式訪問需使用方考慮延遲問題
推薦使用tinyid-client來獲取id,好處如下:
id為本地生成(調(diào)用AtomicLong.addAndGet方法),性能大大增加
client對(duì)server訪問變的低頻,減輕了server的壓力
因?yàn)榈皖l,即便client使用方和server不在一個(gè)機(jī)房,也無須擔(dān)心延遲
即便所有server掛掉,因?yàn)閏lient預(yù)加載了號(hào)段,依然可以繼續(xù)使用一段時(shí)間 注:使用tinyid-client方式,如果client機(jī)器較多頻繁重啟,可能會(huì)浪費(fèi)較多的id,這時(shí)可以考慮使用http方式
推薦db配置兩個(gè)或更多:
db配置多個(gè)時(shí),只要有1個(gè)db存活,則服務(wù)可用 多db配置,如配置了兩個(gè)db,則每次新增業(yè)務(wù)需在兩個(gè)db中都寫入相關(guān)數(shù)據(jù)
tinyid的原理
Id生成系統(tǒng)要點(diǎn)
在簡(jiǎn)單系統(tǒng)中,我們常常使用db的id自增方式來標(biāo)識(shí)和保存數(shù)據(jù),隨著系統(tǒng)的復(fù)雜,數(shù)據(jù)的增多,分庫分表成為了常見的方案,db自增已無法滿足要求。這時(shí)候全局唯一的id生成系統(tǒng)就派上了用場(chǎng)。當(dāng)然這只是id生成其中的一種應(yīng)用場(chǎng)景。那么id生成系統(tǒng)有哪些要求呢?
- 全局唯一的id:無論怎樣都不能重復(fù),這是最基本的要求了
- 高性能:基礎(chǔ)服務(wù)盡可能耗時(shí)少,如果能夠本地生成最好
- 高可用:雖說很難實(shí)現(xiàn)100%的可用性,但是也要無限接近于100%的可用性
- 簡(jiǎn)單易用: 能夠拿來即用,接入方便,同時(shí)在系統(tǒng)設(shè)計(jì)和實(shí)現(xiàn)上要盡可能的簡(jiǎn)單
Tinyid的實(shí)現(xiàn)原理
我們先來看一下最常見的id生成方式,db的auto_increment,相信大家都非常熟悉,我也見過一些同學(xué)在實(shí)戰(zhàn)中使用這種方案來獲取一個(gè)id,這個(gè)方案的優(yōu)點(diǎn)是簡(jiǎn)單,缺點(diǎn)是每次只能向db獲取一個(gè)id,性能比較差,對(duì)db訪問比較頻繁,db的壓力會(huì)比較大。那么是不是可以對(duì)這種方案優(yōu)化一下呢,可否一次向db獲取一批id呢?答案當(dāng)然是可以的。?一批id,我們可以看成是一個(gè)id范圍,例如(1000,2000],這個(gè)1000到2000也可以稱為一個(gè)"號(hào)段",我們一次向db申請(qǐng)一個(gè)號(hào)段,加載到內(nèi)存中,然后采用自增的方式來生成id,這個(gè)號(hào)段用完后,再次向db申請(qǐng)一個(gè)新的號(hào)段,這樣對(duì)db的壓力就減輕了很多,同時(shí)內(nèi)存中直接生成id,性能則提高了很多。那么保存db號(hào)段的表該怎設(shè)計(jì)呢?
DB號(hào)段算法描述
id start_id end_id
1 1000 2000
如上表,我們很容易想到的是db直接存儲(chǔ)一個(gè)范圍(start_id,end_id],當(dāng)這批id使用完畢后,我們做一次update操作,update start_id=2000(end_id), end_id=3000(end_id+1000),update成功了,則說明獲取到了下一個(gè)id范圍。仔細(xì)想想,實(shí)際上start_id并沒有起什么作用,新的號(hào)段總是(end_id,end_id+1000]。所以這里我們更改一下,db設(shè)計(jì)應(yīng)該是這樣的
id biz_type max_id step version
1 1000 2000 1000 0
- 這里我們?cè)黾恿薭iz_type,這個(gè)代表業(yè)務(wù)類型,不同的業(yè)務(wù)的id隔離
- max_id則是上面的end_id了,代表當(dāng)前最大的可用id
- step代表號(hào)段的長度,可以根據(jù)每個(gè)業(yè)務(wù)的qps來設(shè)置一個(gè)合理的長度
- version是一個(gè)樂觀鎖,每次更新都加上version,能夠保證并發(fā)更新的正確性 ?那么我們可以通過如下幾個(gè)步驟來獲取一個(gè)可用的號(hào)段,
- A.查詢當(dāng)前的max_id信息:select id, biz_type, max_id, step, version from tiny_id_info where biz_type='test';
- B.計(jì)算新的max_id: new_max_id = max_id + step
- C.更新DB中的max_id:update tiny_id_info set max_id=#{new_max_id} , verison=version+1 where id=#{id} and max_id=#{max_id} and version=#{version}
- D.如果更新成功,則可用號(hào)段獲取成功,新的可用號(hào)段為(max_id, new_max_id]
- E.如果更新失敗,則號(hào)段可能被其他線程獲取,回到步驟A,進(jìn)行重試
號(hào)段生成方案的簡(jiǎn)單架構(gòu)
如上我們已經(jīng)完成了號(hào)段生成邏輯,那么我們的id生成服務(wù)架構(gòu)可能是這樣的
id生成系統(tǒng)向外提供http服務(wù),請(qǐng)求經(jīng)過我們的負(fù)載均衡router,到達(dá)其中一臺(tái)tinyid-server,從事先加載好的號(hào)段中獲取一個(gè)id,如果號(hào)段還沒有加載,或者已經(jīng)用完,則向db再申請(qǐng)一個(gè)新的可用號(hào)段,多臺(tái)server之間因?yàn)樘?hào)段生成算法的原子性,而保證每臺(tái)server上的可用號(hào)段不重,從而使id生成不重。?可以看到如果tinyid-server如果重啟了,那么號(hào)段就作廢了,會(huì)浪費(fèi)一部分id;同時(shí)id也不會(huì)連續(xù);每次請(qǐng)求可能會(huì)打到不同的機(jī)器上,id也不是單調(diào)遞增的,而是趨勢(shì)遞增的,不過這對(duì)于大部分業(yè)務(wù)都是可接受的。
簡(jiǎn)單架構(gòu)的問題
到此一個(gè)簡(jiǎn)單的id生成系統(tǒng)就完成了,那么是否還存在問題呢?回想一下我們最開始的id生成系統(tǒng)要求,高性能、高可用、簡(jiǎn)單易用,在上面這套架構(gòu)里,至少還存在以下問題:
- 當(dāng)id用完時(shí)需要訪問db加載新的號(hào)段,db更新也可能存在version沖突,此時(shí)id生成耗時(shí)明顯增加
- db是一個(gè)單點(diǎn),雖然db可以建設(shè)主從等高可用架構(gòu),但始終是一個(gè)單點(diǎn)
- 使用http方式獲取一個(gè)id,存在網(wǎng)絡(luò)開銷,性能和可用性都不太好
優(yōu)化辦法如下:
(1)雙號(hào)段緩存
對(duì)于號(hào)段用完需要訪問db,我們很容易想到在號(hào)段用到一定程度的時(shí)候,就去異步加載下一個(gè)號(hào)段,保證內(nèi)存中始終有可用號(hào)段,則可避免性能波動(dòng)。
(2)增加多db支持
db只有一個(gè)master時(shí),如果db不可用(down掉或者主從延遲比較大),則獲取號(hào)段不可用。實(shí)際上我們可以支持多個(gè)db,比如2個(gè)db,A和B,我們獲取號(hào)段可以隨機(jī)從其中一臺(tái)上獲取。那么如果A,B都獲取到了同一號(hào)段,我們?cè)趺幢WC生成的id不重呢?tinyid是這么做的,讓A只生成偶數(shù)id,B只生產(chǎn)奇數(shù)id,對(duì)應(yīng)的db設(shè)計(jì)增加了兩個(gè)字段,如下所示
id biz_type max_id step delta remainder version
1 1000 2000 1000 2 0 0
delta代表id每次的增量,remainder代表余數(shù),例如可以將A,B都delta都設(shè)置2,remainder分別設(shè)置為0,1則,A的號(hào)段只生成偶數(shù)號(hào)段,B是奇數(shù)號(hào)段。通過delta和remainder兩個(gè)字段我們可以根據(jù)使用方的需求靈活設(shè)計(jì)db個(gè)數(shù),同時(shí)也可以為使用方提供只生產(chǎn)類似奇數(shù)的id序列。
(3) 增加tinyid-client
使用http獲取一個(gè)id,存在網(wǎng)絡(luò)開銷,是否可以本地生成id?為此我們提供了tinyid-client,我們可以向tinyid-server發(fā)送請(qǐng)求來獲取可用號(hào)段,之后在本地構(gòu)建雙號(hào)段、id生成,如此id生成則變成純本地操作,性能大大提升,因?yàn)楸镜赜须p號(hào)段緩存,則可以容忍tinyid-server一段時(shí)間的down掉,可用性也有了比較大的提升。
(4) tinyid最終架構(gòu)
最終我們的架構(gòu)可能是這樣的
- tinyid提供http和tinyid-client兩種方式接入
- tinyid-server內(nèi)部緩存兩個(gè)號(hào)段
- 號(hào)段基于db生成,具有原子性
- db支持多個(gè)
- tinyid-server內(nèi)置easy-router選擇db
項(xiàng)目地址
github地址:https://github.com/didi/tinyid
推薦閱讀
為什么阿里巴巴的程序員成長速度這么快,看完他們的內(nèi)部資料我懂了
字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開放下載
刷Github時(shí)發(fā)現(xiàn)了一本阿里大神的算法筆記!標(biāo)星70.5K
月薪在30K以下的Java程序員,可能聽不懂這個(gè)項(xiàng)目;
字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開放分享
看完三件事??
如果你覺得這篇內(nèi)容對(duì)你還蠻有幫助,我想邀請(qǐng)你幫我三個(gè)小忙:
點(diǎn)贊,轉(zhuǎn)發(fā),有你們的 『點(diǎn)贊和評(píng)論』,才是我創(chuàng)造的動(dòng)力。
關(guān)注公眾號(hào) 『 Java斗帝 』,不定期分享原創(chuàng)知識(shí)。
同時(shí)可以期待后續(xù)文章ing??