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í)。可以使用大約69年的時(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-08Springboot?異步任務(wù)和定時(shí)任務(wù)的異步處理
本文介紹了Springboot異步任務(wù)和定時(shí)任務(wù)的異步處理,Springboot?中,異步任務(wù)和定時(shí)任務(wù)是經(jīng)常遇到的處理問(wèn)題方式,為了能夠用好這兩項(xiàng)配置,不干擾正常的業(yè)務(wù),需要對(duì)其進(jìn)行異步化配置。怎么設(shè)置合理的異步處理線程就是其核心和關(guān)鍵,下文詳情需要的朋友可以參考下2022-05-05springboot整合nacos,如何讀取nacos配置文件
這篇文章主要介紹了springboot整合nacos,如何讀取nacos配置文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11SpringBoot自定義配置項(xiàng)過(guò)程
在SpringBoot項(xiàng)目中,通過(guò)在application.properties文件中添加配置項(xiàng),然后使用@ConfigurationProperties注解將這些配置項(xiàng)與實(shí)體Bean進(jìn)行綁定,可以實(shí)現(xiàn)配置項(xiàng)與實(shí)體類字段的自動(dòng)關(guān)聯(lián),進(jìn)而方便地讀取配置文件中的數(shù)據(jù),這種方法不僅簡(jiǎn)化了配置管理2024-11-11java 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-03SpringBoot集成Druid連接池連接MySQL8.0.11
這篇博客簡(jiǎn)單介紹spring boot集成druid連接池的簡(jiǎn)單配置和注意事項(xiàng),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07Java實(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