巧用 CAS,一分鐘實(shí)現(xiàn)分布式 ID 生成器!
有童鞋留言,CAS還有其他應(yīng)有場(chǎng)景嗎?
場(chǎng)景很多,今天分享一個(gè)巧用CAS實(shí)現(xiàn)分布式全局唯一ID生成的場(chǎng)景。
如何快速生成全局唯一ID?
可以借助DB自增鍵(auto inc id),插入一條記錄,生成一個(gè)ID:

這個(gè)方案復(fù)用了數(shù)據(jù)庫的特性,其優(yōu)點(diǎn)為:
- 無需寫額外代碼;
 - 全局唯一;
 - 絕對(duì)遞增;
 - 遞增ID的步長(zhǎng)確定;
 
業(yè)務(wù)早期“并發(fā)量小”,追求“快速實(shí)現(xiàn)”,這么玩沒問題。
這種方案有什么缺點(diǎn)?
- 數(shù)據(jù)庫中記錄數(shù)較多;
 - 生成ID的性能,取決于數(shù)據(jù)庫插入性能;
 - 并發(fā)量大了,數(shù)據(jù)庫扛不?。?/li>
 
可以怎么優(yōu)化?
- DB只保留max-id一條記錄;
 - 增加一層服務(wù),采用批量生成的方式降低數(shù)據(jù)庫的寫壓力,提升整體性能;
 
更具體的操作如下:
步驟一,id-s服務(wù)首先拉取當(dāng)前的max-id。

select max_id from T;步驟二,批量獲取一批ID,放到id-s內(nèi)存里,并將max-id寫回?cái)?shù)據(jù)庫。

update T set max_id=200;如上圖所示,id-s拿到了[100, 200]這一批ID,上游在獲取ID時(shí),不用每次都插入數(shù)據(jù)庫,而是分配完100個(gè)ID后,再修改max-id的值,這樣分配ID的整體性能就增加了100倍。
這種方案還有什么缺點(diǎn)?
服務(wù)沒有做HA,無法保證高可用。需要冗余服務(wù),做集群保證高可用。
冗余服務(wù)可能帶來什么新的問題?
和高并發(fā)庫存扣減出現(xiàn)的問題類似,冗余了服務(wù)后,多個(gè)服務(wù)在啟動(dòng)過程中,進(jìn)行ID批量申請(qǐng)時(shí),可能由于并發(fā)導(dǎo)致數(shù)據(jù)不一致。

select max_id from T;步驟一:兩個(gè)id-s同時(shí)拿到了max-id為100;

update T set max_id=200;步驟二:兩個(gè)id-s同時(shí)對(duì)數(shù)據(jù)庫的max-id進(jìn)行寫回;
寫回max-id成功后,這兩個(gè)id-s都以為自己拿到了[100,200]這一批ID,導(dǎo)致集群會(huì)生成重復(fù)的ID。
導(dǎo)致bug的原因在哪里?
是并發(fā)寫回時(shí),沒有對(duì)max-id的初始值進(jìn)行比對(duì):
- id-s1寫回max-id=200成功的條件是,max-id必須等于100;
 - id-s2寫回max-id=200成功的條件是,max-id也必須等于100;
 
而實(shí)際情況是:
- id-s1寫回時(shí),max-id是100,理應(yīng)寫回成功;
 - id-s2寫回時(shí),max-id已經(jīng)被改成了200,不應(yīng)該寫回成功;
 
如何巧用CAS修復(fù)?
在寫回時(shí)對(duì)max-id的初始條件進(jìn)行比對(duì),就能避免數(shù)據(jù)的不一致,寫回SQL由:
update T set max_id=200;升級(jí)為:
update T set max_id=200
where max_id=100;升級(jí)之后,s1和s2只會(huì)有一個(gè)成功。另一個(gè)失敗之后,重新查詢數(shù)據(jù)庫得到新的max-id為200,再次申請(qǐng)[200,300]的ID,就能夠成功。
CAS優(yōu)化方案的優(yōu)點(diǎn)是:
- 能夠通過水平擴(kuò)展的方式,達(dá)到分布式ID生成服務(wù)的無限性能;
 - 方案簡(jiǎn)潔性;
 - 保證不會(huì)生成重復(fù)的ID;
 
知其然,知其所以然。
思路比結(jié)論更重要。















 
 
 









 
 
 
 