java中雪花算法時(shí)鐘回?fù)軉?wèn)題解決
雪花算法(Snowflake)是Twitter開(kāi)源的一種分布式唯一ID生成算法,用于在分布式系統(tǒng)中生成全局唯一的ID。雪花算法生成的ID是一個(gè)64位的整數(shù),通常表示為長(zhǎng)整型(long)。
雪花算法的結(jié)構(gòu)
雪花算法生成的ID由以下幾部分組成:
- 符號(hào)位(1位):始終為0,保證生成的ID為正數(shù)。
- 時(shí)間戳(41位):記錄生成ID的時(shí)間戳,精確到毫秒級(jí)??梢允褂么蠹s69年的時(shí)間。
- 機(jī)器ID(10位):標(biāo)識(shí)生成ID的機(jī)器,可以支持1024臺(tái)機(jī)器。
- 序列號(hào)(12位):同一毫秒內(nèi)生成的多個(gè)ID的序列號(hào),可以支持每毫秒生成4096個(gè)ID。
時(shí)鐘回?fù)軉?wèn)題
時(shí)鐘回?fù)軉?wèn)題是指在分布式系統(tǒng)中,由于各種原因(如NTP時(shí)間同步),某些節(jié)點(diǎn)的系統(tǒng)時(shí)間可能會(huì)回退到過(guò)去的時(shí)間點(diǎn)。這會(huì)導(dǎo)致雪花算法生成的ID出現(xiàn)重復(fù),因?yàn)闀r(shí)間戳部分會(huì)重復(fù)。
解決時(shí)鐘回?fù)軉?wèn)題的方法
等待機(jī)制:
- 當(dāng)檢測(cè)到時(shí)鐘回?fù)軙r(shí),生成器可以等待時(shí)間追上上次生成ID的時(shí)間戳,然后再生成新的ID。這種方法簡(jiǎn)單直接,但可能會(huì)導(dǎo)致生成器在等待期間無(wú)法生成新的ID。
擴(kuò)展位:
- 在ID結(jié)構(gòu)中增加額外的位來(lái)處理時(shí)鐘回?fù)?。例如,可以使用額外的位來(lái)記錄時(shí)鐘回?fù)艿拇螖?shù),從而避免ID重復(fù)。
預(yù)留時(shí)間戳:
- 在生成ID時(shí),預(yù)留一些時(shí)間戳范圍,用于處理時(shí)鐘回?fù)?。例如,可以預(yù)留一些時(shí)間戳范圍,當(dāng)檢測(cè)到時(shí)鐘回?fù)軙r(shí),使用預(yù)留的時(shí)間戳生成新的ID。
邏輯時(shí)鐘:
- 使用邏輯時(shí)鐘(如Lamport時(shí)鐘或Vector時(shí)鐘)代替物理時(shí)鐘。邏輯時(shí)鐘可以保證在分布式系統(tǒng)中事件的順序,避免時(shí)鐘回?fù)軉?wèn)題。
示例代碼
以下是一個(gè)使用等待機(jī)制解決時(shí)鐘回?fù)軉?wèn)題的雪花算法實(shí)現(xiàn)示例:
public class SnowflakeIdGenerator {
private final long twepoch = 1288834974657L; // 起始時(shí)間戳,例如Twitter的Snowflake起始時(shí)間
private final long workerIdBits = 10L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long timestampLeftShift = sequenceBits + workerIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private long workerId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
this.workerId = workerId;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
// 時(shí)鐘回?fù)?,等待時(shí)間追上
long offset = lastTimestamp - timestamp;
if (offset <= 5) {
try {
wait(offset << 1);
timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) |
(workerId << workerIdShift) |
sequence;
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1);
System.out.println(idGenerator.nextId());
}
}
代碼解釋
檢測(cè)時(shí)鐘回?fù)?/strong>:
- 在生成ID時(shí),首先獲取當(dāng)前時(shí)間戳,并與上次生成ID的時(shí)間戳進(jìn)行比較。
- 如果當(dāng)前時(shí)間戳小于上次生成ID的時(shí)間戳,說(shuō)明發(fā)生了時(shí)鐘回?fù)堋?/li>
等待機(jī)制:
- 如果時(shí)鐘回?fù)艿臅r(shí)間差小于等于5毫秒,生成器會(huì)等待時(shí)間追上上次生成ID的時(shí)間戳。
- 如果時(shí)鐘回?fù)艿臅r(shí)間差大于5毫秒,拋出異常,拒絕生成ID。
生成新的ID:
- 如果時(shí)鐘沒(méi)有回?fù)?,或者等待時(shí)間追上后,生成新的ID。
總結(jié)
時(shí)鐘回?fù)軉?wèn)題是分布式系統(tǒng)中使用雪花算法生成唯一ID時(shí)需要解決的一個(gè)重要問(wèn)題。通過(guò)使用等待機(jī)制、擴(kuò)展位、預(yù)留時(shí)間戳或邏輯時(shí)鐘等方法,可以有效避免時(shí)鐘回?fù)軐?dǎo)致的ID重復(fù)問(wèn)題。在實(shí)際應(yīng)用中,可以根據(jù)具體需求選擇合適的解決方案。
到此這篇關(guān)于java中雪花算法時(shí)鐘回?fù)軉?wèn)題解決的文章就介紹到這了,更多相關(guān)java 雪花算法時(shí)鐘回?fù)軆?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot連接多個(gè)數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方法
有時(shí)候一個(gè)SpringBoot項(xiàng)目需要同時(shí)連接兩個(gè)數(shù)據(jù)庫(kù),本文就來(lái)介紹一下springboot連接多個(gè)數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-08-08
Springboot?異步任務(wù)和定時(shí)任務(wù)的異步處理
本文介紹了Springboot異步任務(wù)和定時(shí)任務(wù)的異步處理,Springboot?中,異步任務(wù)和定時(shí)任務(wù)是經(jīng)常遇到的處理問(wèn)題方式,為了能夠用好這兩項(xiàng)配置,不干擾正常的業(yè)務(wù),需要對(duì)其進(jìn)行異步化配置。怎么設(shè)置合理的異步處理線(xiàn)程就是其核心和關(guān)鍵,下文詳情需要的朋友可以參考下2022-05-05
springboot整合nacos,如何讀取nacos配置文件
這篇文章主要介紹了springboot整合nacos,如何讀取nacos配置文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
SpringBoot自定義配置項(xiàng)過(guò)程
在SpringBoot項(xiàng)目中,通過(guò)在application.properties文件中添加配置項(xiàng),然后使用@ConfigurationProperties注解將這些配置項(xiàng)與實(shí)體Bean進(jìn)行綁定,可以實(shí)現(xiàn)配置項(xiàng)與實(shí)體類(lèi)字段的自動(dòng)關(guān)聯(lián),進(jìn)而方便地讀取配置文件中的數(shù)據(jù),這種方法不僅簡(jiǎn)化了配置管理2024-11-11
java Timer 定時(shí)每天凌晨1點(diǎn)執(zhí)行任務(wù)
這篇文章主要介紹了java Timer 定時(shí)每天凌晨1點(diǎn)執(zhí)行任務(wù)的代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09
解決IntellIJ IDEA提示內(nèi)存不足的圖文教程
現(xiàn)在越來(lái)越多的人投入了 IntellIJ Idea 的懷抱, 它給我們的日常開(kāi)發(fā)帶來(lái)了諸多便利,但是我們?cè)陂_(kāi)發(fā)過(guò)程中,總是能碰到idea內(nèi)存不足問(wèn)題,所以本文給大家介紹了解決IntellIJ IDEA提示內(nèi)存不足的圖文教程,需要的朋友可以參考下2025-03-03
SpringBoot集成Druid連接池連接MySQL8.0.11
這篇博客簡(jiǎn)單介紹spring boot集成druid連接池的簡(jiǎn)單配置和注意事項(xiàng),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07
Java實(shí)戰(zhàn)之基于I/O流設(shè)計(jì)的圖書(shū)管理系統(tǒng)
這篇文章主要介紹了Java實(shí)戰(zhàn)之基于I/O流設(shè)計(jì)的圖書(shū)館管理系統(tǒng),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04
詳解關(guān)于spring bean名稱(chēng)命名的那些事
每個(gè)bean都有一個(gè)或者多個(gè)標(biāo)識(shí)符,這些標(biāo)識(shí)符在容器中必須是唯一的,這篇文章主要給大家介紹了關(guān)于spring bean名稱(chēng)命名的那些事,需要的朋友可以參考下2021-07-07

