一、基礎知識
什么是雪花算法
Snowflake 中文的意思是雪花,所以常被稱為雪花算法,是 Twitter 開源的分布式 ID 生成算法。
Twitter 雪花算法生成后是一個 64bit 的 long 型的數值,組成部分引入了時間戳,基本保持了自增
SnowFlake 算法的優點:
- 高性能高可用:生成時不依賴於數據庫,完全在內存中生成
- 高吞吐:每秒鍾能生成數百萬的自增 ID
- ID 自增:存入數據庫中,索引效率高
SnowFlake 算法的缺點:
依賴與系統時間的一致性,如果系統時間被回調,或者改變,可能會造成 ID 沖突或者重復。
雪花算法組成
包含四個組成部分:
- 不使用:1bit,最高位是符號位,0 表示正,1 表示負,固定為 0
- 時間戳:41bit,毫秒級的時間戳(41 位的長度可以使用 69 年)
- 標識位:5bit 數據中心 ID,5bit 工作機器 ID,兩個標識位組合起來最多可以支持部署 1024 個節點
- 序列號:12bit 遞增序列號,表示節點毫秒內生成重復,通過序列號表示唯一,12bit 每毫秒可產生 4096 個 ID
二、雪花算法適用場景
因為雪花算法有序自增,保障了 MySQL 中 B+ Tree 索引結構插入高性能,所以,日常業務使用中,雪花算法更多是被應用在數據庫的主鍵 ID 和業務關聯主鍵。
三、雪花算法生成 ID 重復問題
new對象(下面提供的方法)時,不要在循環內創建對象
四、工具類(附運行main)
/** * @Description 雪花算法 * */ public class IdWorker { private final long twepoch = 1288834974657L; private final long workerIdBits = 5L; private final long datacenterIdBits = 5L; private final long maxWorkerId = -1L ^ (-1L << workerIdBits); private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); private final long sequenceBits = 12L; private final long workerIdShift = sequenceBits; private final long datacenterIdShift = sequenceBits + workerIdBits; private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; private final long sequenceMask = -1L ^ (-1L << sequenceBits); private long workerId; private long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; public IdWorker(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 (該方法是線程安全的) * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); //如果當前時間小於上一次ID生成的時間戳,說明系統時鍾回退過這個時候應當拋出異常 if (timestamp < lastTimestamp) { throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //如果是同一時間生成的,則進行毫秒內序列 if (lastTimestamp == timestamp) { //如果毫秒相同,則從0遞增生成序列號 sequence = (sequence + 1) & sequenceMask; //毫秒內序列溢出 if (sequence == 0) { //阻塞到下一個毫秒,獲得新的時間戳 timestamp = tilNextMillis(lastTimestamp); } } //時間戳改變,毫秒內序列重置 else { sequence = 0L; } //上次生成ID的時間截 lastTimestamp = timestamp; //移位並通過或運算拼到一起組成64位的ID 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) { IdWorker idWorker = new IdWorker(0, 0); for (int i = 0; i < 100; i++) { long id = idWorker.nextId(); System.out.println(id); } } }