【Java】Snowflake (雪花演算法工具類)
阿新 • • 發佈:2022-01-26
Java 雪花演算法工具類
SnowFlake(Twitter_Snowflake)的結構如下(每部分用-分開): 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 1位標識,由於long基本型別在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0。 41位時間戳(毫秒級),不是儲存當前時間的時間戳,而是儲存時間戳的差值(當前時間戳 - 開始時間戳); 可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69。 10位的資料機器位,可以部署在1024個節點,包括5位datacenterId和5位workerId。 12位毫秒內的計數序列,計數順序號支援每個節點每毫秒(同一機器,同一時間戳)產生4096個ID。 加起來64位剛好是一個long,整體上按照時間自增排序,且分散式系統內不會產生ID碰撞(由資料中心ID和機器ID作區分),效率較高。
import lombok.extern.slf4j.Slf4j; import java.text.MessageFormat; @Slf4j public class Snowflake { // ==============================Fields=========================================== /** * 開始時間戳 (2000-01-01 00:00:00) */ private static final long TWEPOCH = 946656000000L; /** * 機器id所佔的位數 5 */ private static final long WORKER_ID_BITS = 5L; /** * 資料標識id所佔的位數 5 */ private static final long DATA_CENTER_ID_BITS = 5L; /** * 支援的最大機器id,結果是 31 */ private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); /** * 支援的最大資料標識id,結果是 31 */ private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS); /** * 序列在id中佔的位數 */ private static final long SEQUENCE_BITS = 12L; /** * 機器ID向左移12位 */ private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; /** * 資料標識id向左移17位(12+5) */ private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; /** * 時間戳向左移22位(5+5+12) */ private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS; /** * 生成序列的掩碼,這裡為4095 (0b111111111111=0xfff=4095) */ private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS); /** * 步長 1024 */ private static final long STEP_SIZE = 1024; /** * unsigned int max value */ private static final long UINT_MAX_VALUE = 0xffffffffL; /** * 工作機器ID(0~31) */ private long workerId; /** * 工作機器ID 計數器 */ private long workerIdFlags = 0L; /** * 資料中心ID(0~31) */ private long dataCenterId; /** * 資料中心ID 計數器 */ private long dataCenterIdFlags = 0L; /** * 毫秒內序列(0~4095) */ private long sequence = 0L; /** * 毫秒內序列基數[0|1024|2048|3072] */ private long basicSequence = 0L; /** * 上次生成ID的時間戳 */ private long lastTimestamp = -1L; /** * 工作模式 */ private final WorkMode workMode; public enum WorkMode { NON_SHARED, RATE_1024, RATE_4096; } //==============================Constructors===================================== public Snowflake() { this(0, 0, WorkMode.RATE_4096); } /** * 建構函式 * @param workerId 工作ID (0~31) * @param dataCenterId 資料中心ID (0~31) */ public Snowflake(long workerId, long dataCenterId) { this(workerId, dataCenterId, WorkMode.RATE_4096); } /** * 建構函式 * @param workerId 工作ID (0~31) * @param dataCenterId 資料中心ID (0~31) * @param workMode 工作模式 */ public Snowflake(long workerId, long dataCenterId, WorkMode workMode) { this.workMode = workMode; if (workerId > MAX_WORKER_ID || workerId < 0) { throw new IllegalArgumentException(MessageFormat.format("worker Id can't be greater than {0} or less than 0", MAX_WORKER_ID)); } if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) { throw new IllegalArgumentException(MessageFormat.format("datacenter Id can't be greater than {0} or less than 0", MAX_DATA_CENTER_ID)); } this.workerId = workerId; this.workerIdFlags = setSpecifiedBitTo1(this.workerIdFlags, this.workerId); this.dataCenterId = dataCenterId; this.dataCenterIdFlags = setSpecifiedBitTo1(this.dataCenterIdFlags, this.dataCenterId); } // ==============================Methods========================================== /** * 獲取機器id * * @return 所屬機器的id */ public long getWorkerId() { return workerId; } /** * 獲取資料中心id * * @return 所屬資料中心id */ public long getDataCenterId() { return dataCenterId; } /** * 獲得下一個ID (該方法是執行緒安全的) * * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); //如果當前時間小於上一次ID生成的時間戳,說明系統時鐘回退過這個時候應當丟擲異常 if (timestamp < this.lastTimestamp) { if (timestamp > TWEPOCH) { if (WorkMode.NON_SHARED == this.workMode) { nonSharedClockBackwards(timestamp); } else if (WorkMode.RATE_1024 == this.workMode) { rate1024ClockBackwards(timestamp); } else { throw new RuntimeException(MessageFormat.format("Clock moved backwards. Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp)); } } else { throw new RuntimeException(MessageFormat.format("Clock moved backwards. Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp)); } } //如果是同一時間生成的,則進行毫秒內序列 if (this.lastTimestamp == timestamp) { this.sequence = (this.sequence + 1) & SEQUENCE_MASK; //毫秒內序列溢位 if (this.sequence == 0) { //阻塞到下一個毫秒,獲得新的時間戳 timestamp = tilNextMillis(this.lastTimestamp); } } //時間戳改變,毫秒內序列重置 else { this.sequence = this.basicSequence; } //上次生成ID的時間戳 this.lastTimestamp = timestamp; //移位並通過或運算拼到一起組成64位的ID return ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | (this.dataCenterId << DATA_CENTER_ID_SHIFT) | (this.workerId << WORKER_ID_SHIFT) | this.sequence; } /** * 阻塞到下一個毫秒,直到獲得新的時間戳 * * @param lastTimestamp 上次生成ID的時間戳 * @return 當前時間戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp0; do { timestamp0 = timeGen(); } while (timestamp0 <= lastTimestamp); return timestamp0; } /** * 返回以毫秒為單位的當前時間 * * @return 當前時間(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } /** * 嘗試解決時鐘回撥<br>【* 僅用於 單機生成不對外 的情況 *】 * * @param timestamp 當前時間戳 * @return void */ private void nonSharedClockBackwards(long timestamp) { if (this.dataCenterIdFlags < UINT_MAX_VALUE || this.workerIdFlags < UINT_MAX_VALUE) { //如果僅用於生成不重複的數值,嘗試變更 dataCenterId 或 workerId 修復時鐘回撥問題 log.warn("Clock moved backwards. Refusing to generate id for {} milliseconds", lastTimestamp - timestamp); //先嚐試變更 dataCenterId,當 dataCenterId 輪詢一遍之後,嘗試變更 workerId 並重置 dataCenterId if (this.dataCenterIdFlags >= UINT_MAX_VALUE) { if (++this.workerId > MAX_WORKER_ID) { this.workerId = 0L; } this.workerIdFlags = setSpecifiedBitTo1(this.workerIdFlags, this.workerId); // 重置 dataCenterId 和 dataCenterIdFlags this.dataCenterIdFlags = this.dataCenterId = 0L; } else { if (++this.dataCenterId > MAX_DATA_CENTER_ID) { this.dataCenterId = 0L; } } this.dataCenterIdFlags = setSpecifiedBitTo1(this.dataCenterIdFlags, this.dataCenterId); this.lastTimestamp = -1L; log.warn("Try to fix the clock moved backwards. timestamp : {}, worker Id : {}, datacenter Id : {}", timestamp, workerId, dataCenterId); } else { throw new RuntimeException(MessageFormat.format("Clock moved backwards. Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp)); } } /** * 嘗試解決時鐘回撥<br>【* 僅用於每毫秒生成量 不大於 1024 的情況 *】 * * @param timestamp 當前時間戳 * @return void */ private void rate1024ClockBackwards(long timestamp) { if (this.basicSequence > (SEQUENCE_MASK - STEP_SIZE)) { throw new RuntimeException(MessageFormat.format("Clock moved backwards. Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp)); } log.warn("Clock moved backwards. Refusing to generate id for {} milliseconds", lastTimestamp - timestamp); this.basicSequence += STEP_SIZE; this.lastTimestamp = -1L; log.warn("Try to fix the clock moved backwards. timestamp : {}, basicSequence : {}", timestamp, basicSequence); } /** * Set the specified bit to 1 * * @param value raw long value * @param index bit index (From 0~31) * @return long value */ private long setSpecifiedBitTo1(long value, long index) { return value |= (1L << index); } /** * Set the specified bit to 0 * * @param value raw long value * @param index bit index (From 0~31) * @return long value */ private long setSpecifiedBitTo0(long value, long index) { return value &= ~(1L << index); } }