雪花算法(Snowflake)是Twitter公司为满足其分布式系统需求而开发的一种全局唯一ID生成算法。该算法于2010年开源,因其简单高效的特点,在分布式系统
中得到广泛应用。
标准的雪花算法生成的64位ID由以下部分组成:
|
1 2 3 4 5 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0| 41位时间戳 | 数据中心 | 机器 | 序列号 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
详细分解:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
public class SnowflakeIdGenerator { // 基准时间戳(可自定义) private final long epoch = 1609459200000L; // 2021-01-01 00:00:00 // 各部分的位数 private final long workerIdBits = 5L; private final long datacenterIdBits = 5L; private final long sequenceBits = 12L; // 最大值计算 private final long maxWorkerId = -1L ^ (-1L << workerIdBits); private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 移位偏移量 private final long workerIdShift = sequenceBits; private final long datacenterIdShift = sequenceBits + workerIdBits; private final long timestampShift = sequenceBits + workerIdBits + datacenterIdBits; // 序列号掩码 private final long sequenceMask = -1L ^ (-1L << sequenceBits); // 工作节点参数 private final long workerId; private final long datacenterId; // 序列号状态 private long sequence = 0L; private long lastTimestamp = -1L; /** * 构造函数 * @param workerId 工作节点ID (0-31) * @param datacenterId 数据中心ID (0-31) */ public SnowflakeIdGenerator(long workerId, long datacenterId) { // 参数校验 if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException( String.format("Worker ID must be between 0 and %d", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException( String.format("Datacenter ID must be between 0 and %d", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } /** * 生成下一个ID */ public synchronized long nextId() { long timestamp = timeGen(); // 时钟回拨处理 if (timestamp < lastTimestamp) { throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } // 同一毫秒内序列号递增 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; // 序列号溢出,等待下一毫秒 if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { // 新毫秒序列号重置 sequence = 0L; } lastTimestamp = timestamp; // 组装ID return ((timestamp - epoch) << timestampShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; } /** * 阻塞到下一毫秒 */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 获取当前时间戳 */ protected long timeGen() { return System.currentTimeMillis(); } /** * 解析ID中的信息 */ public void parseId(long id) { long timestamp = (id >> timestampShift) + epoch; long datacenterId = (id >> datacenterIdShift) & maxDatacenterId; long workerId = (id >> workerIdShift) & maxWorkerId; long sequence = id & sequenceMask; System.out.println("ID解析结果:"); System.out.println("生成时间:" + new Date(timestamp)); System.out.println("数据中心ID:" + datacenterId); System.out.println("工作节点ID:" + workerId); System.out.println("序列号:" + sequence); } }<img class="look-more-preCode contentImg-no-view" title="" src="https://longsheng.org/wp-content/uploads/2026/06/b38ae2688123fb0d1caee1cb8365a424.png" alt="" /> |
-1L ^ (-1L << n) 计算n位能表示的最大值synchronized保证多线程安全|
1 2 3 |
// 设置为系统上线时间,延长可用期限 private final long epoch = LocalDateTime.of(2023, 1, 1, 0, 0) .toInstant(ZoneOffset.UTC).toEpochMilli(); |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public synchronized long nextId() { long timestamp = timeGen(); // 增强的时钟回拨处理 if (timestamp < lastTimestamp) { long offset = lastTimestamp - timestamp; if (offset <= 5) { // 小范围回拨,等待 try { wait(offset << 1); // 等待两倍偏移时间 timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨处理失败"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("时钟回拨等待被中断", e); } } else { // 大范围回拨,直接报错 throw new RuntimeException(String.format( "严重时钟回拨:%d毫秒,系统时间可能被手动调整", offset)); } } // ...其余逻辑不变 } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// 使用ThreadLocalRandom替代同步块 private long nextIdOptimized() { long timestamp = timeGen(); if (timestamp < lastTimestamp.get()) { throw new RuntimeException("时钟回拨"); } // 时间戳相同则增加序列号 if (timestamp == lastTimestamp.get()) { sequence.set((sequence.get() + 1) & sequenceMask); if (sequence.get() == 0) { timestamp = tilNextMillis(lastTimestamp.get()); } } else { // 时间戳变化,重置序列号 sequence.set(ThreadLocalRandom.current().nextInt(100)); } lastTimestamp.set(timestamp); return ((timestamp - epoch) << timestampShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence.get(); } |
特点:
两种模式:
根据业务需求调整位数分配:
|
1 2 3 |
// 例如:调整时间戳为秒级,增加序列号位数 private final long timestampBits = 32L; // 约136年 private final long sequenceBits = 20L; // 每秒100万ID |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# K8s StatefulSet配置示例 kind: StatefulSet spec: serviceName: "id-service" replicas: 3 template: spec: containers: - name: app env: - name: WORKER_ID valueFrom: fieldRef: fieldPath: metadata.name # 将pod名称如id-service-0的序号作为workerId |
3. 压力测试:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Test void performanceTest() { SnowflakeIdGenerator generator = new SnowflakeIdGenerator(1, 1); long start = System.currentTimeMillis(); int count = 1_000_000; for (int i = 0; i < count; i++) { generator.nextId(); } long duration = System.currentTimeMillis() - start; System.out.printf("生成%d个ID耗时:%dms,QPS:%.2f万/秒%n", count, duration, count / (duration / 1000.0) / 10000); } |
解决方案:
预防措施:
雪花算法是分布式系统ID生成的经典解决方案,Java实现需要注意:
对于超高并发场景,可以考虑结合号段模式或使用改进版算法如Leaf。实际应用中应建立完善的监控体系,确保ID生成服务的稳定性。
from:https://blog.csdn.net/weixin_52578852/article/details/146931647