java中使用雪花算法(Snowflake)為分布式系統(tǒng)生成全局唯一ID代碼示例
(全局唯一ID的解決方案有很多種,這里主要是介紹和學(xué)習(xí)Snowflake算法)
什么是雪花算法(Snowflake)
雪花算法(Snowflake Algorithm)是由Twitter公司在2010年左右提出的一種分布式ID生成算法,主要用于生成全局唯一且趨勢(shì)遞增的ID。這種算法生成的ID是一個(gè)64位的長整型數(shù)字,具有很高的性能與擴(kuò)展性,特別適合于分布式環(huán)境下的主鍵生成場(chǎng)景,比如數(shù)據(jù)庫表主鍵、消息隊(duì)列的Message ID等。
實(shí)現(xiàn)原理
Snowflake算法的原理主要體現(xiàn)在它生成64位ID的結(jié)構(gòu)上,主要?jiǎng)澐譃槿缦聨讉€(gè)部分:
0 | 00000000000000000000000000000000000000000 | 00000 | 00000 | 000000000000
- 1bit-符號(hào)位:
第1位通常固定為0,表示生成的ID都是正數(shù)。
- 41bit-時(shí)間戳部分:
從第2位到第42位(共41位)存儲(chǔ)時(shí)間戳信息,精確到毫秒級(jí)別。時(shí)間戳可以是自定義的一個(gè)起始時(shí)間點(diǎn)(如Twitter使用的是2010-11-04的某一時(shí)刻),這樣可以通過比較ID中的時(shí)間戳部分來判斷事件發(fā)生的先后順序。41位的時(shí)間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69。
- 10bit-工作機(jī)器ID(5bit數(shù)據(jù)中心ID+5bit機(jī)器ID):
從第43位到第52位(共10位)存儲(chǔ)工作機(jī)器ID或者數(shù)據(jù)中心ID。這部分可以進(jìn)一步細(xì)分為兩部分,例如前5位標(biāo)識(shí)數(shù)據(jù)中心ID,后5位標(biāo)識(shí)工作節(jié)點(diǎn)ID。這樣可以支持32(0~31)個(gè)數(shù)據(jù)中心以及每個(gè)數(shù)據(jù)中心內(nèi)部的32(0~31)個(gè)工作節(jié)點(diǎn),足夠覆蓋大規(guī)模分布式系統(tǒng)的節(jié)點(diǎn)標(biāo)識(shí)。
- 12bit-序列號(hào)部分:
從第53位到第64位(共12位)存儲(chǔ)同一節(jié)點(diǎn)同一毫秒內(nèi)生成的序列號(hào),這意味著同一個(gè)節(jié)點(diǎn)在同毫秒內(nèi)可以生成最多4096個(gè)不同的ID(2^12)。
當(dāng)生成ID時(shí),首先獲取當(dāng)前時(shí)間戳,然后加上工作節(jié)點(diǎn)ID以及序列號(hào)。如果在同一毫秒內(nèi)有新的請(qǐng)求,則序列號(hào)加1。若序列號(hào)達(dá)到最大值,則等待下一毫秒再進(jìn)行分配,從而確保在同一節(jié)點(diǎn)內(nèi)生成的ID是唯一的
雪花算法的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
全局唯一性:雪花算法生成的ID是全局唯一的,這在分布式系統(tǒng)中非常重要,可以避免因ID沖突而導(dǎo)致的數(shù)據(jù)不一致問題。
遞增有序:由于ID中包含時(shí)間戳部分,所以生成的ID是遞增有序的。這有助于數(shù)據(jù)庫插入性能的優(yōu)化,因?yàn)橛行虻腎D可以減少數(shù)據(jù)庫的頁分裂,提高寫入效率。
靈活性:雪花算法允許自定義配置工作機(jī)器ID和數(shù)據(jù)中心ID的位數(shù),可以根據(jù)實(shí)際部署環(huán)境調(diào)整這些配置,以支持不同規(guī)模的分布式系統(tǒng)。
高效性:算法本身實(shí)現(xiàn)簡單,生成ID的速度快,能夠滿足高并發(fā)場(chǎng)景下的需求。
缺點(diǎn):
時(shí)鐘依賴:雪花算法依賴于系統(tǒng)時(shí)鐘來生成時(shí)間戳部分。如果系統(tǒng)時(shí)鐘出現(xiàn)回?fù)芑蚱?,可能?huì)導(dǎo)致生成的ID不唯一或有序性受到破壞。雖然可以通過一些機(jī)制來處理時(shí)鐘回?fù)軉栴},但時(shí)鐘漂移仍然是一個(gè)潛在的風(fēng)險(xiǎn)。
機(jī)器ID沖突:如果部署的工作節(jié)點(diǎn)數(shù)量超過了算法中定義的機(jī)器ID位數(shù)所能表示的范圍,就會(huì)發(fā)生機(jī)器ID沖突。這需要在設(shè)計(jì)系統(tǒng)時(shí)預(yù)先規(guī)劃好機(jī)器ID的分配和管理。
缺乏安全性:雪花算法生成的ID本身并不包含加密或簽名信息,因此容易受到惡意篡改。如果ID的安全性要求較高,需要在生成ID后添加額外的加密或簽名措施。
擴(kuò)展性限制:由于雪花算法的ID結(jié)構(gòu)是固定的,因此在某些情況下可能會(huì)受到擴(kuò)展性的限制。例如,如果未來需要添加更多的元數(shù)據(jù)到ID中,或者需要支持更大的分布式系統(tǒng)規(guī)模,可能需要重新設(shè)計(jì)ID生成算法。
因此,為了更全面地解決雪花算法的缺陷問題,可能需要采取額外的措施,例如:
增強(qiáng)時(shí)鐘同步:使用NTP(Network Time Protocol)或其他時(shí)鐘同步機(jī)制來確保各個(gè)節(jié)點(diǎn)之間的時(shí)鐘盡可能準(zhǔn)確同步。
增加機(jī)器ID的靈活性:設(shè)計(jì)一種更靈活的方式來分配和管理機(jī)器ID,以便支持更多的工作節(jié)點(diǎn)和數(shù)據(jù)中心。
安全性考慮:對(duì)生成的ID進(jìn)行加密或簽名,以防止惡意篡改。
綜上所述,雪花算法在分布式系統(tǒng)中具有廣泛的應(yīng)用價(jià)值,其全局唯一性和遞增有序性使得它成為生成唯一ID的優(yōu)選方案之一。然而,在使用雪花算法時(shí)也需要注意其潛在的缺點(diǎn),并根據(jù)實(shí)際需求進(jìn)行配置和優(yōu)化。
Snowflake算法生成ID的Java代碼示例
以下是Snowflake算法的一個(gè)java簡化版實(shí)現(xiàn):
public class SnowflakeIdWorker { // 起始的時(shí)間戳(自定義,例如系統(tǒng)上線時(shí)間) private final long twepoch = 1288834974657L; // 機(jī)器id所占的位數(shù) private final long workerIdBits = 5L; // 數(shù)據(jù)標(biāo)識(shí)id所占的位數(shù) private final long datacenterIdBits = 5L; // 最大機(jī)器ID private final long maxWorkerId = -1L ^ (-1L << workerIdBits); // 最大數(shù)據(jù)標(biāo)識(shí)ID private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 序列在id中占的位數(shù) private final long sequenceBits = 12L; // 機(jī)器ID左移12位 private final long workerIdShift = sequenceBits; // 數(shù)據(jù)標(biāo)識(shí)id左移17位(12+5) private final long datacenterIdShift = sequenceBits + workerIdBits; // 時(shí)間截左移22位(5+5+12) private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 序列的掩碼,這里為4095 (0b111111111111=4095) private final long sequenceMask = -1L ^ (-1L << sequenceBits); // 上次生成ID的時(shí)間截 private long lastTimestamp = -1L; // 序列號(hào) private long sequence = 0L; // 工作機(jī)器ID private final long workerId; // 數(shù)據(jù)中心ID private final long datacenterId; public SnowflakeIdWorker(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } // 生成ID public synchronized long nextId() { long timestamp = timeGen(); // 如果當(dāng)前時(shí)間小于上一次ID生成的時(shí)間戳,說明系統(tǒng)時(shí)鐘回退,拋出異常 if (timestamp < lastTimestamp) { throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } // 如果時(shí)間戳相同,則序列號(hào)自增 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; // 序列號(hào)溢出,等待下一毫秒 if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { // 時(shí)間戳改變,序列號(hào)重置為0 sequence = 0L; } // 更新最后的時(shí)間戳 lastTimestamp = timestamp; // 移位并通過或運(yùn)算拼到一起組成64位的ID return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; } // 獲取當(dāng)前時(shí)間戳 protected long timeGen() { return System.currentTimeMillis(); } // 等待下一個(gè)毫秒 protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } public static void main(String[] args) { SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 1); for (int i = 0; i < 5; i++) { long id = idWorker.nextId(); System.out.println(Long.toBinaryString(id)); System.out.println(id); } } }
代碼輸出:
這段代碼實(shí)現(xiàn)了雪花算法的核心邏輯。在nextId()
方法中,它首先獲取當(dāng)前時(shí)間戳,然后檢查時(shí)間戳是否小于上一次生成ID時(shí)的時(shí)間戳,如果是,則拋出異常,因?yàn)檫@意味著系統(tǒng)時(shí)鐘回退,可能會(huì)導(dǎo)致ID生成出現(xiàn)混亂。如果時(shí)間戳相同,則序列號(hào)自增,并檢查是否溢出,如果溢出則等待下一個(gè)毫秒。如果時(shí)間戳不同,則重置序列號(hào)。最后,將時(shí)間戳、數(shù)據(jù)中心ID、機(jī)器ID和序列號(hào)按照各自的偏移量左移,然后進(jìn)行位或運(yùn)算,組合成一個(gè)64位的ID。
(注:關(guān)于數(shù)據(jù)中心ID、機(jī)器ID,根據(jù)實(shí)際情況來進(jìn)行配置。)
總結(jié)
到此這篇關(guān)于java中使用雪花算法(Snowflake)為分布式系統(tǒng)生成全局唯一ID的文章就介紹到這了,更多相關(guān)java雪花算法生成全局唯一ID內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java?Map.values()方法之如何獲取Map集合中的所有鍵值對(duì)象
這篇文章主要介紹了Java?Map.values()方法之如何獲取Map集合中的所有鍵值對(duì)象問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03ShardingSphere jdbc實(shí)現(xiàn)分庫分表核心概念詳解
這篇文章主要為大家介紹了ShardingSphere jdbc實(shí)現(xiàn)分庫分表核心概念詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12JSON反序列化Long變Integer或Double的問題及解決
這篇文章主要介紹了JSON反序列化Long變Integer或Double的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01IDEA POM文件配置profile實(shí)現(xiàn)不同環(huán)境切換的方法步驟
這篇文章主要介紹了IDEA POM文件配置profile實(shí)現(xiàn)不同環(huán)境切換的方法步驟2024-03-03在SpringBoot中使用MongoDB完成數(shù)據(jù)存儲(chǔ)
本文主要介紹了在SpringBoot中如惡化使用MongoDB完成數(shù)據(jù)存儲(chǔ),接下來這篇我們將圍繞MongoDB進(jìn)行,MongoDB是一個(gè)開源的,面向文檔的NoSQL數(shù)據(jù)庫管理系統(tǒng),使用類似JSON的BSON(二進(jìn)制JSON)格式來存儲(chǔ)數(shù)據(jù),具有靈活的數(shù)據(jù)模型和強(qiáng)大的查詢功能,需要的朋友可以參考下2023-11-11