Java使用雪花id生成算法詳解
什么是雪花算法
雪花算法的本質(zhì)為生成一個64位長度的具有自增性的分布式全局唯一id。在64bits中,會對不同段的位進(jìn)行劃分??煞譃椋?/p>
- 符號段
- 時間戳段
- 機(jī)器碼段(data center + worker)
- 自增序列號段
位段詳解
- 第一位 : 符號位,正數(shù)為0。
- [2, 42] : 41位時間戳位,表明id的生成時間點(diǎn)(完整時間戳: 起始時間戳 + 41位時間戳)。41位最多能表示的時間為: (2^41-1) / (1000 * 60 * 60 * 24 * 365) 約等為69.73年。
- [43, 47] : 5位data center id。data center id + worker id 共10位,最多能表示1024個機(jī)器。不同機(jī)器保證機(jī)器碼段的位值不同即可。
- [48, 52] : 5位worker id。data center id + worker id 共10位,最多能表示1024個機(jī)器。不同機(jī)器保證機(jī)器碼段的位值不同即可。
- [53, 64] : 12位自增序列號,用于區(qū)分同一毫秒內(nèi)生成的id。序列號范圍: [0, 2^12-1],最多有2^12個,即4096個。
優(yōu)點(diǎn)
- 算法簡單,基于內(nèi)存,生成效率高
- 支持分布式環(huán)境下的多節(jié)點(diǎn)服務(wù)(機(jī)器碼段),秒內(nèi)可生成百萬個唯一id
- 基于時間戳 與 同時間戳下自增序列號,生成的id具有自增性
- 具有業(yè)務(wù)定制性,根據(jù)業(yè)務(wù)的不同可以對不同段的位數(shù)進(jìn)行變更。比如業(yè)務(wù)持續(xù)時長不會那么久,就可以將時間戳段減少位數(shù),補(bǔ)充給自增序列段,使每一毫秒能生成更多的id。
問題
依賴服務(wù)器時間。若服務(wù)器時鐘回?fù)?,可能會?dǎo)致生成的id重復(fù)??稍诖a中新增lastTimeMillis字段,在獲取nextId時根據(jù)系統(tǒng)當(dāng)前時間進(jìn)行判斷解決。
但若不進(jìn)行持久化處理,服務(wù)重啟后發(fā)生時鐘回?fù)芤琅f會出現(xiàn)重復(fù)問題。
實(shí)際應(yīng)用
- mybatis plus:使用雪花算法生成id:@TableId(value = “id”, type = IdType.ID_WORKER)。id字段若不指定類型,默認(rèn)使用雪花算法生成id
- Hutool工具包:IdUtil.createSnowflake(workerId, datacenterId);
具體實(shí)現(xiàn)
/** * Created by QQ.Cong on 2022-07-22 / 9:48 * * @author: CongQingquan * @Description: Snowflake util */ public class SnowflakeUtils { // ============================== Basic field ==============================// // Datacenter id private long datacenterId; // Worker id private long workerId; // Increment sequence private long sequence; // ============================== Bits ==============================// // Bits of datacenter id private long datacenterIdBits; // Bits of worker id private long workerIdBits; // Bits of sequence private long sequenceBits; // ============================== Largest ==============================// // Largest datacenter id private long largestDatacenterId; // Largest worker id private long largestWorkerId; // Largest sequence private long largestSequence; // ============================== Shift ==============================// // Left shift num of worker id private long workerIdShift; // Left shift num of datacenter id private long datacenterIdShift; // Left shift num of timestamp private long timestampShift; // ============================== Other ==============================// // Epoch private long epoch; // The timestamp that last get snowflake id private long lastTimestamp; // ============================== End ==============================// public SnowflakeUtils(long dataCenterId, long workerId) { // Default epoch: 2022-07-22 00:00:00 this(1658419200000L, -1L, dataCenterId, workerId, 5L, 5L, 5L); } public SnowflakeUtils(long epoch, long lastTimestamp, long datacenterId, long workerId, long datacenterIdBits, long workerIdBits, long sequenceBits) { this.epoch = epoch; this.lastTimestamp = lastTimestamp; this.datacenterId = datacenterId; this.workerId = workerId; this.sequence = 0L; this.datacenterIdBits = datacenterIdBits; this.workerIdBits = workerIdBits; this.sequenceBits = sequenceBits; this.largestDatacenterId = ~(-1L << datacenterIdBits); this.largestWorkerId = ~(-1L << workerIdBits); this.largestSequence = ~(-1L << sequenceBits); if (datacenterId > largestDatacenterId || datacenterId < 0) { throw new IllegalArgumentException( String.format("The datacenter id param can't be greater than %s or less than 0", largestDatacenterId)); } if (workerId > largestWorkerId || workerId < 0) { throw new IllegalArgumentException( String.format("The worker id param can't be greater than %s or less than 0", largestWorkerId)); } this.workerIdShift = sequenceBits; this.datacenterIdShift = workerIdShift + workerIdBits; this.timestampShift = datacenterIdShift + datacenterIdBits; } /** * Get snowflake id * @return */ public synchronized long nextId() { long timestamp = System.currentTimeMillis(); // 若時鐘回退 if (timestamp < lastTimestamp) { throw new RuntimeException( "System clock moved backward, cannot to generate snowflake id"); } // 若當(dāng)前毫秒內(nèi)多次生成雪花id if (timestamp == lastTimestamp) { sequence = (sequence + 1) & largestSequence; // 序列溢出 if (sequence == 0) { timestamp = waitUntilNextMilli(timestamp); } } // 若當(dāng)前毫秒內(nèi)首次生成雪花id else { sequence = 0L; } // 更新獲取雪花id的時間戳 lastTimestamp = timestamp; // 生成雪花id (通過位或運(yùn)算符進(jìn)行拼接) return ((timestamp - epoch) << timestampShift) // 時間戳段 | (datacenterId << datacenterIdShift) // 機(jī)器碼段 | (workerId << workerIdShift) // 機(jī)器碼段 | sequence; // 自增序列段 } /** * Wait until next millisecond * @param lastTimestamp * @return */ private long waitUntilNextMilli(long lastTimestamp) { long currentTimeMillis; do { currentTimeMillis = System.currentTimeMillis(); } while (currentTimeMillis <= lastTimestamp); return currentTimeMillis; } /** * Get util instance * @param dataCenterId * @param workerId * @return */ public static SnowflakeUtils getInstance(long dataCenterId, long workerId) { return new SnowflakeUtils(dataCenterId, workerId); } }
到此這篇關(guān)于Java使用雪花id生成算法詳解的文章就介紹到這了,更多相關(guān)Java雪花id生成算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis使用@one和@Many實(shí)現(xiàn)一對一及一對多關(guān)聯(lián)查詢
本文主要介紹了Mybatis使用@one和@Many實(shí)現(xiàn)一對一及一對多關(guān)聯(lián)查詢,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09java并發(fā)包工具CountDownLatch源碼分析
這篇文章主要為大家介紹了java并發(fā)包工具CountDownLatch源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10SpringBoot自動配置Quartz的實(shí)現(xiàn)步驟
本文主要介紹了SpringBoot自動配置Quartz的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11SpringBoot普通類獲取spring容器中bean的操作
這篇文章主要介紹了SpringBoot普通類獲取spring容器中bean的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09