MyBatis-Plus內(nèi)置雪花算法出現(xiàn)主鍵重復(fù),給你推薦這款優(yōu)化后的分布式ID生成器!
大家好,我是飄渺。昨天小伙伴使用Mybaits-Plus開發(fā)的項(xiàng)目線上(集群、K8S)出現(xiàn)了主鍵重復(fù)問題,其報(bào)錯(cuò)如下:
圖片
Mybatis-Plus啟動(dòng)時(shí)會(huì)通過com.baomidou.mybatisplus.core.toolkit.Sequence類的getMaxWorkerId()和getDatacenterId()方法來初始化workerId和dataCenterId()。
protected long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuilder mpid = new StringBuilder();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (StringUtils.isNotBlank(name)) {
mpid.append(name.split("@")[0]);
}
return (long)(mpid.toString().hashCode() & '\uffff') % (maxWorkerId + 1L);
}
protected long getDatacenterId(long maxDatacenterId) {
...省略部分代碼...
byte[] mac = network.getHardwareAddress();
if (null != mac) {
id = (255L & (long)mac[mac.length - 2] | 65280L & (long)mac[mac.length - 1] << 8) >> 6;
id %= maxDatacenterId + 1L;
}
return id;
}
通過代碼可知,workerID是根據(jù)虛擬機(jī)名稱生成,dataCenterId是根據(jù)mac地址生成,這2個(gè)東西部署在Docker環(huán)境中就很有可能重復(fù)。
本文不去探討怎么解決這個(gè)問題,而是給你推薦另外一個(gè)經(jīng)過優(yōu)化后的雪花算法,可以非常方便集成在你項(xiàng)目中并替換掉Mybatis-Plus的ID生成邏輯。
概述
在軟件開發(fā)過程中,我們經(jīng)常會(huì)遇到需要生成全局唯一流水號(hào)的場(chǎng)景,例如各種流水號(hào)和分庫分表的分布式主鍵ID。特別是在使用MySQL數(shù)據(jù)庫時(shí),除了要求流水號(hào)具有“全局唯一”性外,還需要具備“遞增趨勢(shì)”,以減少M(fèi)ySQL的數(shù)據(jù)頁分裂,從而降低數(shù)據(jù)庫IO壓力并提升服務(wù)器性能。
因此,在項(xiàng)目中通常需要引入一種算法,能夠生成滿足“全局唯一”、“遞增趨勢(shì)”和“高性能”要求的數(shù)據(jù)。
關(guān)于全局分布式ID的生成,網(wǎng)上有很多相關(guān)文章。其中最常見的方法是借助第三方開源組件實(shí)現(xiàn),如百度開源的Uidgenerator、滴滴開源的TinyID、美團(tuán)開源的Leaf以及雪花算法SnowFlake等。然而,大部分開源組件都需要依賴數(shù)據(jù)庫或Redis中間件來實(shí)現(xiàn),對(duì)于非特大型項(xiàng)目來說可能過于繁重。因此,我更傾向于在項(xiàng)目中使用雪花算法SnowFlake來生成全局唯一ID。
標(biāo)準(zhǔn)版雪花算法網(wǎng)上已經(jīng)有很多解讀文章了,此處就不再贅述了。
然而,標(biāo)準(zhǔn)版的雪花算法存在 時(shí)鐘敏感 問題。由于ID生成與當(dāng)前操作系統(tǒng)時(shí)間戳綁定(利用了時(shí)間的單調(diào)遞增性),當(dāng)操作系統(tǒng)的時(shí)鐘出現(xiàn)回?fù)軙r(shí),生成的ID可能會(huì)重復(fù)(盡管通常不會(huì)人為地回?fù)軙r(shí)鐘,但服務(wù)器可能會(huì)出現(xiàn)偶發(fā)的“時(shí)鐘漂移”現(xiàn)象)。
如果要要解決這個(gè)問題,我們可以在獲取 ID 時(shí)記錄當(dāng)前的時(shí)間戳。然后在下一次獲取 ID 時(shí),比較當(dāng)前時(shí)間戳和上次記錄的時(shí)間戳。如果發(fā)現(xiàn)當(dāng)前時(shí)間戳小于上次記錄的時(shí)間戳,說明出現(xiàn)了時(shí)鐘回?fù)墁F(xiàn)象,此時(shí)可以拒絕服務(wù)并等待時(shí)間戳追上記錄值。
因此,在項(xiàng)目中我們不能直接使用標(biāo)準(zhǔn)版的雪花算法,而需要尋找一個(gè)改良后的方案。
這里我推薦大家使用開源分布式事務(wù)處理組件Seata的改良方案,它完美的解決了雪花算法時(shí)鐘敏感的問題,并且代碼簡潔,可以非常方便集成在你項(xiàng)目中。
下面讓我們來分析一下Seata改進(jìn)后的方案。
Seata的優(yōu)化方案
在原版雪花算法中,分布式ID的格式是這樣的。
圖片
雪花算法主要是利用時(shí)間的單調(diào)遞增特性,并且與操作系統(tǒng)的時(shí)間戳?xí)r刻綁定,一旦出現(xiàn)時(shí)間“回退”,則打破了時(shí)間 “單調(diào)遞增”這個(gè)前提,所以可能會(huì)出現(xiàn)重復(fù)。
而在改良后的Seata方案中,其ID格式是這樣的。
圖片
通過觀察Seata代碼,我們可以發(fā)現(xiàn)它只是簡單地調(diào)整了節(jié)點(diǎn)ID和時(shí)間戳的位置。那么這樣做的目的是什么呢?
答案是通過這種方式解除了算法與操作系統(tǒng)時(shí)間戳的強(qiáng)綁定關(guān)系。生成器僅在初始化時(shí)獲取系統(tǒng)時(shí)間戳作為初始時(shí)間戳,之后不再與系統(tǒng)時(shí)間戳同步。生成器的遞增僅由序列號(hào)的遞增驅(qū)動(dòng)。例如,當(dāng)序列號(hào)的當(dāng)前值達(dá)到4095時(shí),下一個(gè)請(qǐng)求到來時(shí),序列號(hào)將溢出12位空間并重新歸零,同時(shí)溢出的進(jìn)位將加到時(shí)間戳上,使時(shí)間戳+1。因此,時(shí)間戳和序列號(hào)實(shí)際上可以視為一個(gè)整體。
這樣,時(shí)間戳和序列號(hào)在內(nèi)存中是連續(xù)存儲(chǔ)的,可以使用一個(gè)AtomicLong來同時(shí)保存它們。下面是相關(guān)核心代碼的示例:
/**
* timestamp and sequence mix in one Long
* highest 11 bit: not used
* middle 41 bit: timestamp
* lowest 12 bit: sequence
*/
private AtomicLong timestampAndSequence;
/**
* The number of bits occupied by sequence
*/
private final int sequenceBits = 12;
/**
* init first timestamp and sequence immediately
*/
private void initTimestampAndSequence() {
long timestamp = getNewestTimestamp();
long timestampWithSequence = timestamp << sequenceBits;
this.timestampAndSequence = new AtomicLong(timestampWithSequence);
}
代碼解釋:
1. 在初始化方法中,獲取當(dāng)前時(shí)間戳getNewestTimestamp()以后將其左移12位,留出了序列號(hào)的位置。
2. 而Long類型轉(zhuǎn)化成二進(jìn)制以后是64位,前11位不使用,中間的41位代表時(shí)間戳,后面的12位代表序列號(hào)。
最高11位在初始化時(shí)就直接確定好,之后不再變化,核心代碼如下:
/**
* init workerId
* @param workerId if null, then auto generate one
*/
private void initWorkerId(Long workerId) {
if (workerId == null) {
workerId = generateWorkerId();
}
if (workerId > maxWorkerId || workerId < 0) {
String message = String.format("worker Id can't be greater than %d or less than 0", maxWorkerId);
throw new IllegalArgumentException(message);
}
this.workerId = workerId << (timestampBits + sequenceBits);
}
/**
* auto generate workerId, try using mac first, if failed, then randomly generate one
* @return workerId
*/
private long generateWorkerId() {
try {
return generateWorkerIdBaseOnMac();
} catch (Exception e) {
return generateRandomWorkerId();
}
}
/**
* use lowest 10 bit of available MAC as workerId
* @return workerId
* @throws Exception when there is no available mac found
*/
private long generateWorkerIdBaseOnMac() throws Exception {
Enumeration<NetworkInterface> all = NetworkInterface.getNetworkInterfaces();
while (all.hasMoreElements()) {
NetworkInterface networkInterface = all.nextElement();
boolean loopBack = networkInterface.isLoopback();
boolean isVirtual = networkInterface.isVirtual();
if (loopBack || isVirtual) {
continue;
}
byte[] mac = networkInterface.getHardwareAddress();
return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF);
}
throw new RuntimeException("no available mac found");
}
代碼解讀:
- 算法規(guī)定了節(jié)點(diǎn)ID最長為10位,2的10次方是1024,所以可以服務(wù)1024臺(tái)機(jī)器,體現(xiàn)在數(shù)字上的取值范圍是為[0,1023);
- 在原版雪花算法中,如果未指定節(jié)點(diǎn)ID,會(huì)截取本地IPv4地址的低10位作為節(jié)點(diǎn)ID,這樣在生成實(shí)踐中如果出現(xiàn)IP的第4個(gè)字節(jié)和第3個(gè)字節(jié)的低2位一樣就會(huì)重復(fù)。如:192.168.4.10 和 192.168.8.10
- 新版算法generateWorkerIdBaseOnMac()是從從本機(jī)網(wǎng)卡的MAC地址截取低10位,最后通過(mac[4] & 0B11) << 8) | (mac[5] & 0xFF)保證其取值范圍最大值為1023,算法有點(diǎn)難懂,分步解釋:mac[4] 和mac[5] 是無符號(hào)8位整數(shù)的變量,其取值范圍是[0,255)(mac[4] & 0B11) 運(yùn)算會(huì)保留 mac[4] 的最后兩位00,01,10,11,也就是取值范圍為 0 到 3。(mac[4] & 0B11) << 8。左移 8 位相當(dāng)于乘以 256,所以結(jié)果的取值范圍是 0 到 3 * 256 = 0 到 768。(mac[5] & 0xFF) 最大值就是0xFF, 也就是取值范圍是 0 到 255所以最后結(jié)果的取值范圍是從 0 到 768 | 255 = 1023。
- 計(jì)算出節(jié)點(diǎn)ID以后,將其左移,this.workerId = workerId << (timestampBits + sequenceBits),這樣就完成了算法ID的組裝。
最后看看生成ID的算法
private final int timestampBits = 41;
private final int sequenceBits = 12;
private final long timestampAndSequenceMask = ~(-1L << (timestampBits + sequenceBits));
public long nextId() {
// 獲得遞增后的時(shí)間戳和序列號(hào)
long next = timestampAndSequence.incrementAndGet();
// 截取低53位
long timestampWithSequence = next & timestampAndSequenceMask;
// 跟先前保存好的高11位進(jìn)行一個(gè)或的位運(yùn)算
return workerId | timestampWithSequence;
}
看完Seata雪花算法的實(shí)現(xiàn)邏輯,你覺得怎么樣呢?反正我只會(huì)直呼 ”臥槽,牛皮“~
通過對(duì)Seata改良算法代碼的解讀,可以知道,算法生成器僅在啟動(dòng)時(shí)獲取了一次系統(tǒng)時(shí)鐘,可以說是弱依賴于操作系統(tǒng)時(shí)鐘,這樣在運(yùn)行期間,生成器不再受時(shí)鐘回?fù)艿挠绊憽?/p>
同時(shí)由于序列號(hào)有12位,最大取值范圍是[0,4095]。
如果在當(dāng)前毫秒下序列號(hào)生成到了 4096 ,這個(gè)時(shí)候序列號(hào)回重新歸0,同時(shí)讓時(shí)間戳+1,也就是 "借用"下一個(gè)時(shí)間戳的序列號(hào)空間,這種超前消費(fèi)會(huì)不會(huì)導(dǎo)致生成器內(nèi)的時(shí)間戳大大超前于系統(tǒng)的時(shí)間戳,從而導(dǎo)致重啟時(shí)ID重復(fù)呢?
理論上有,實(shí)際上并不會(huì)。因?yàn)橐_(dá)到這個(gè)效果,也就意味著生成器的QPS得持續(xù)穩(wěn)定在4096/ms,約400W/s之上,這得什么場(chǎng)景才能有這樣的流量呢?(12306在2020年春運(yùn)期間高峰QPS約為170W/s,2020年雙11淘寶TPS為58.3W/s) 就算有了,瓶頸一定不在生成器這里。
通過對(duì)Seata改良算法代碼的解讀,我們可以了解到算法生成器僅在啟動(dòng)時(shí)獲取一次系統(tǒng)時(shí)鐘,因此它在運(yùn)行期間對(duì)操作系統(tǒng)時(shí)鐘的依賴相對(duì)較弱。這意味著生成器不會(huì)受到時(shí)鐘回?fù)艿挠绊憽?/p>
此外,根據(jù)序列號(hào)的位數(shù)為12位,其取值范圍為[0, 4095]。
如果在當(dāng)前毫秒內(nèi)序列號(hào)生成到了4096,這時(shí)序列號(hào)會(huì)重新歸0,并且時(shí)間戳?xí)黾?,即"借用"下一個(gè)時(shí)間戳的序列號(hào)空間。這種超前消費(fèi)是否會(huì)導(dǎo)致生成器內(nèi)部的時(shí)間戳大大超前于系統(tǒng)的時(shí)間戳,從而導(dǎo)致在重啟時(shí)出現(xiàn)重復(fù)的ID呢?
理論上來說,這種情況是有可能發(fā)生的。然而,在實(shí)際情況下并不會(huì)出現(xiàn)這種問題。因?yàn)橐_(dá)到這種效果,也就意味著生成器的每秒請(qǐng)求數(shù)(QPS)需要持續(xù)穩(wěn)定在4096次以上,相當(dāng)于每秒處理約400萬個(gè)請(qǐng)求。這樣高的流量場(chǎng)景是非常罕見的,而且即使存在這樣的流量,天塌下來有高個(gè)子頂著,一定會(huì)是其他組件先出問題。
Seata雪花算法的 “缺陷”
經(jīng)過觀察,我們可以發(fā)現(xiàn)一個(gè)問題:Seata改良版的算法在單節(jié)點(diǎn)內(nèi)部確實(shí)是單調(diào)遞增的,但是在多實(shí)例部署時(shí),它不再保證全局單調(diào)遞增。這是因?yàn)楣?jié)點(diǎn)ID在生成的ID中占據(jù)了高位,因此節(jié)點(diǎn)ID較大的生成的ID一定大于節(jié)點(diǎn)ID較小的生成的ID,與它們的生成時(shí)間先后順序無關(guān)。
相比之下,原版雪花算法將時(shí)間戳放在高位,并且始終追隨系統(tǒng)時(shí)鐘,可以確保早期生成的ID小于后期生成的ID。只有當(dāng)兩個(gè)節(jié)點(diǎn)恰好在同一時(shí)間戳生成ID時(shí),兩個(gè)ID的大小才由節(jié)點(diǎn)ID決定。
從這個(gè)角度來看,新版算法是否存在問題呢?
關(guān)于這個(gè)問題,官方已經(jīng)給出了結(jié)論:
新版算法的確不具備全局的單調(diào)遞增性,但這不影響我們的初衷(減少數(shù)據(jù)庫的頁分裂)。這個(gè)結(jié)論看起來有點(diǎn)違反直覺,但可以被證明。
現(xiàn)在讓我們來進(jìn)一步優(yōu)化和解釋這個(gè)結(jié)論。
B+樹原理
在證明之前我們需要先回顧一下數(shù)據(jù)庫頁分裂的相關(guān)知識(shí)(基于B+數(shù)索引的MySQL InnoDB引擎)。
在B+樹索引中,主鍵索引的葉子節(jié)點(diǎn)除了保存鍵的值之外,還保存了數(shù)據(jù)行的完整記錄。葉子節(jié)點(diǎn)之間以雙向鏈表的形式連接在一起。葉子節(jié)點(diǎn)在物理存儲(chǔ)上被組織為數(shù)據(jù)頁,每個(gè)數(shù)據(jù)頁最多可以存儲(chǔ)N條行記錄。
圖片
B+樹的特性要求左邊的節(jié)點(diǎn)的鍵值小于右邊節(jié)點(diǎn)的鍵值。如果現(xiàn)在要插入一條ID為25的記錄,會(huì)發(fā)生什么呢?(假設(shè)每個(gè)數(shù)據(jù)頁只能容納4條記錄)答案是會(huì)導(dǎo)致頁分裂,如下圖所示:
圖片
頁分裂對(duì)IO操作不友好,需要?jiǎng)?chuàng)建新的數(shù)據(jù)頁,并復(fù)制和轉(zhuǎn)移舊數(shù)據(jù)頁中的部分記錄。因此,我們應(yīng)該盡量避免頁分裂的發(fā)生。
如果你想直觀地了解B+樹節(jié)點(diǎn)分裂的過程,建議訪問以下網(wǎng)站:
B+ Tree Visualization -> https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html
理想的情況下,主鍵ID最好是順序遞增的(例如把主鍵設(shè)置為auto_increment),這樣就只會(huì)在當(dāng)前數(shù)據(jù)頁放滿了的時(shí)候,才需要新建下一頁,雙向鏈表永遠(yuǎn)是順序尾部增長的,不會(huì)有中間的節(jié)點(diǎn)發(fā)生分裂的情況。
最糟糕的情況下,主鍵ID是隨機(jī)無序生成的(例如java中一個(gè)UUID字符串),這種情況下,新插入的記錄會(huì)隨機(jī)分配到任何一個(gè)數(shù)據(jù)頁,如果該頁已滿,就會(huì)觸發(fā)頁分裂。
如果主鍵ID由標(biāo)準(zhǔn)版雪花算法生成,最好的情況下,是每個(gè)時(shí)間戳內(nèi)只有一個(gè)節(jié)點(diǎn)在生成ID,這時(shí)候算法的效果等同于理想情況的順序遞增,即跟auto_increment無差。最壞的情況下,是每個(gè)時(shí)間戳內(nèi)所有節(jié)點(diǎn)都在生成ID,這時(shí)候算法的效果接近于無序(但仍比UUID的完全無序要好得多,因?yàn)閣orkerId只有10位決定了最多只有1024個(gè)節(jié)點(diǎn))。實(shí)際生產(chǎn)中,算法的效果取決于業(yè)務(wù)流量,并發(fā)度越低,算法越接近理想情況。
在理想情況下,主鍵ID最好是按順序遞增的(例如使用auto_increment設(shè)置主鍵),這樣只有在當(dāng)前數(shù)據(jù)頁已滿時(shí)才需要?jiǎng)?chuàng)建下一頁,雙向鏈表的增長總是在尾部進(jìn)行的,不會(huì)導(dǎo)致中間節(jié)點(diǎn)的分裂。
在最糟糕的情況下,主鍵ID是隨機(jī)無序生成的(例如在Java中使用UUID字符串),這種情況下,新插入的記錄會(huì)被隨機(jī)分配到任意一個(gè)數(shù)據(jù)頁,如果該頁已滿,則觸發(fā)頁分裂。
這也是為什么不推薦使用UUID作為主鍵ID的原因,UUID會(huì)導(dǎo)致頻繁出現(xiàn)頁裂變,影響數(shù)據(jù)庫性能。
如果主鍵ID由標(biāo)準(zhǔn)版雪花算法生成,最理想的情況是每個(gè)時(shí)間戳內(nèi)只有一個(gè)節(jié)點(diǎn)生成ID,這種情況下算法的效果與理想情況的順序遞增相同,即與auto_increment沒有區(qū)別。最糟糕的情況是每個(gè)時(shí)間戳內(nèi)的所有節(jié)點(diǎn)都在生成ID,這種情況下算法的效果接近于無序(但仍比完全無序的UUID要好得多,因?yàn)閣orkerId只有10位,限制了節(jié)點(diǎn)數(shù)量最多為1024個(gè))。在實(shí)際生產(chǎn)環(huán)境中,算法的效果取決于業(yè)務(wù)流量,較低的并發(fā)度會(huì)使算法接近理想情況。
那么,Seata改良版的雪花算法又是如何呢?
Seata 改良算法會(huì)導(dǎo)致頻繁頁裂變嗎?
新版算法從全局角度來看,生成的ID是無序的。然而,對(duì)于每個(gè)節(jié)點(diǎn)而言,它所生成的ID序列是嚴(yán)格單調(diào)遞增的。由于節(jié)點(diǎn)ID是有限的,因此最多可以劃分出1024個(gè)子序列,每個(gè)子序列都是單調(diào)遞增的。
對(duì)于數(shù)據(jù)庫而言,在初始階段接收到的ID可能是無序的,來自各個(gè)子序列的ID會(huì)混合在一起。假設(shè)節(jié)點(diǎn)ID的值是遞增的,初始階段的效果如下圖所示:
圖片
假設(shè)此時(shí)出現(xiàn)了一個(gè)worker1-seq2的ID,由于數(shù)據(jù)頁已經(jīng)存滿,會(huì)觸發(fā)一次頁分裂,如下圖所示:
圖片
然而,分裂之后發(fā)生了一件有趣的事情。對(duì)于worker1而言,后續(xù)的seq3、seq4由于可以直接放入數(shù)據(jù)頁,不會(huì)再觸發(fā)頁分裂。而seq5只需要像順序遞增一樣,在新建的頁中進(jìn)行鏈接。值得注意的是,由于worker1的后續(xù)ID都比worker2的ID小,它們不會(huì)被分配到worker2及其之后的節(jié)點(diǎn),因此不會(huì)導(dǎo)致后續(xù)節(jié)點(diǎn)的頁分裂。同樣地,由于是單調(diào)遞增,它們也不會(huì)被分配到worker1當(dāng)前節(jié)點(diǎn)的前面,因此不會(huì)導(dǎo)致前面節(jié)點(diǎn)的頁分裂。
在這里,我們稱具有這種性質(zhì)的子序列達(dá)到了穩(wěn)態(tài),意味著該子序列已經(jīng)"穩(wěn)定"下來,其后續(xù)增長只會(huì)發(fā)生在子序列的尾部,而不會(huì)引起其他節(jié)點(diǎn)的頁分裂。同樣的情況也可以推廣到其他子序列上。無論初始階段數(shù)據(jù)庫接收到的ID有多么混亂,在有限次頁分裂之后,雙向鏈表總能達(dá)到這樣一個(gè)穩(wěn)定的終態(tài):
圖片
到達(dá)終態(tài)后,后續(xù)的ID只會(huì)在該ID所屬的子序列上進(jìn)行順序增長,而不會(huì)造成頁分裂。該狀態(tài)下的順序增長與auto_increment的順序增長的區(qū)別是,前者有1024個(gè)增長位點(diǎn)(各個(gè)子序列的尾部),后者只有尾部一個(gè)。
小結(jié)
綜上所述,改進(jìn)版的雪花算法雖然不具備全局單調(diào)遞增的特性,但在同一節(jié)點(diǎn)下能夠保持單調(diào)遞增。此外,經(jīng)過幾次數(shù)據(jù)頁分裂后,它會(huì)達(dá)到一個(gè)穩(wěn)定狀態(tài),不會(huì)頻繁觸發(fā)數(shù)據(jù)庫的頁分裂。同時(shí),該算法仍然滿足高性能和全局唯一的要求。因此,完全可以將改進(jìn)版的雪花算法引入到項(xiàng)目中使用。
然而,需要注意的是,在實(shí)際業(yè)務(wù)系統(tǒng)中,最好將此算法應(yīng)用于那些需要長期保存數(shù)據(jù)的場(chǎng)景,而對(duì)于需要頻繁刪除的表則不太適用。
這是因?yàn)樵撍惴ɡ们捌诘捻摲至?,逐漸將不同子序列分離,從而實(shí)現(xiàn)算法的收斂到穩(wěn)定狀態(tài)。如果頻繁刪除數(shù)據(jù),會(huì)觸發(fā)數(shù)據(jù)庫的頁合并操作,這會(huì)阻礙數(shù)據(jù)的收斂。在極端情況下,剛剛分離的數(shù)據(jù)可能會(huì)立即發(fā)生頁合并,導(dǎo)致數(shù)據(jù)無法保持穩(wěn)定狀態(tài)。因此,在使用改進(jìn)版的雪花算法時(shí)需要謹(jǐn)慎考慮業(yè)務(wù)需求和數(shù)據(jù)操作的頻率。
DailyMart集成全局ID算法
DailyMart項(xiàng)目中涉及到多個(gè)場(chǎng)景需要使用全局唯一ID,因此我已經(jīng)將Seata改進(jìn)版的雪花算法通過自定義Starter的方式集成到了項(xiàng)目中。使用時(shí)只需要調(diào)用IdUtils.nextId()方法即可獲取全局唯一ID,你可以參考源代碼進(jìn)行具體實(shí)現(xiàn)。
圖片
同時(shí),之前的文章中提到了在使用Mybatis-Plus時(shí),由于沒有正確配置worker-id和datacenter-id參數(shù),導(dǎo)致生成的ID可能會(huì)出現(xiàn)重復(fù)?;诖宋疫€在datasources公共模塊中替換了Mybatis-Plus的ID生成算法,直接使用Seata改進(jìn)后的雪花算法。
以下為代碼具體實(shí)現(xiàn):
public class CustomIdGenerator implements IdentifierGenerator {
@Override
public Number nextId(Object entity) {
return IdUtils.nextId();
}
}
/**
* 替換Mybatis-plus的算法生成器
*/
@Bean
public IdentifierGenerator identifierGenerator() {
return new CustomIdGenerator();
}