
雪花算法是 Twitter 开源的一种分布式 ID 生成算法用于在分布式系统中生成全局唯一、趋势递增的 64 位长整型 ID。它的核心思想是使用一个 64 位的 long 类型数字作为全局唯一 ID并将这 64 位划分为若干部分每部分存储不同的信息。ID结构1.符号位1位始终为0用于标识ID是正数。2.时间戳41位时间戳占据了整个ID的41位精确到毫秒级可以支持69年的时间戳。这使得雪花算法能够支持未来数十年的唯一性。时间戳部分还提供了排序的功能可以根据时间戳来对数据进行排序。3.机器ID10位工作机器ID占据了ID的10位可以支持最多1024个工作节点。这使得在同一台机器上运行的不同应用程序实例可以使用不同的工作机器ID来生成唯一的ID。4.序列号12位序列号占据了ID的12位可以支持每个节点每毫秒产生4096个唯一的ID。这使得在同一台机器上运行的不同应用程序实例可以生成唯一的ID即使在毫秒级别内也能保证唯一性。雪花算法的特点优点高性能完全在内存中生成没有数据库 I/O 开销单节点每秒可生成数万甚至数十万个 ID。全局唯一在分布式环境下通过机器 ID 和时间戳的组合保证唯一性。趋势递增生成 ID 按时间趋势递增有利于数据库索引和排序。无外部依赖不依赖数据库、Redis 等外部存储部署简单。位数可控可根据业务需求调整时间戳、机器 ID、序列号的位数。缺点强依赖系统时钟如果机器时钟回拨可能导致 ID 冲突或重复。单毫秒序列号有限单节点每毫秒最多生成 4096 个 ID超出会阻塞到下一毫秒极端高并发下可能成为瓶颈。机器 ID 需要手动分配在大规模集群中机器 ID 的管理和分配需要额外机制。长整型在部分语言中精度问题如 JavaScript 中Number类型最大安全整数为2^53 - 1雪花 ID 超过该值会导致精度丢失通常需要序列化为字符串传输。以下是雪花算法的简单实现public class SnowflakeIdGenerator { // 起始时间戳2020-01-01 00:00:00 private final long epoch 1577836800000L; // 各部分位数 private final long workerIdBits 10L; private final long sequenceBits 12L; // 最大机器 ID1023 private final long maxWorkerId -1L ^ (-1L workerIdBits); // 位移量 private final long workerIdShift sequenceBits; private final long timestampShift sequenceBits workerIdBits; // 序列号掩码4095 private final long sequenceMask -1L ^ (-1L sequenceBits); private long workerId; private long sequence 0L; private long lastTimestamp -1L; public SnowflakeIdGenerator(long workerId) { if (workerId maxWorkerId || workerId 0) { throw new IllegalArgumentException( String.format(workerId 必须在 0 到 %d 之间, maxWorkerId)); } this.workerId workerId; } public synchronized long nextId() { long timestamp currentTime(); // 处理时钟回拨如果当前时间小于上次生成 ID 的时间则等待 if (timestamp lastTimestamp) { long offset lastTimestamp - timestamp; if (offset 5) { // 如果回拨在 5ms 内等待追赶 try { Thread.sleep(offset); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(等待时钟追赶时被中断, e); } timestamp currentTime(); } else { throw new RuntimeException( String.format(时钟回拨超过 5ms拒绝生成 ID。当前时间差%d ms, offset)); } } // 同一毫秒内生成 ID if (timestamp lastTimestamp) { sequence (sequence 1) sequenceMask; if (sequence 0) { // 当前毫秒序列号用完等待下一毫秒 timestamp waitNextMillis(lastTimestamp); } } else { // 新的毫秒序列号重置 sequence 0L; } lastTimestamp timestamp; return ((timestamp - epoch) timestampShift) // 时间戳左移 22 位 | (workerId workerIdShift) // 机器 ID 左移 12 位 | sequence; // 序列号占据低 12 位 } private long waitNextMillis(long lastTimestamp) { long timestamp currentTime(); while (timestamp lastTimestamp) { timestamp currentTime(); } return timestamp; } private long currentTime() { return System.currentTimeMillis(); } }public class Main { public static void main(String[] args) { SnowflakeIdGenerator generator new SnowflakeIdGenerator(1); for (int i 0; i 10; i) { System.out.println(generator.nextId()); } } }时钟回拨问题及解决方案时钟回拨是雪花算法钟最最需要注意的问题当系统的时间回到过去时 NTP 同步、手动调整、虚拟机漂移可能产生重复时间戳导致ID重复解决方案1.直接sleep到正确时间只适合小的幅度2.检测到回拨直接拒绝生成3.持久化最后时间戳 将其存到文件或数据库 重启时检查避免宕机恢复后时间回拨。4.弄个逻辑时钟 max 判断当前时间和上一个时间 避免回退