Skip to content

雪花算法 ≠ 绝对唯一,只能做到「在正确配置 + 不越界使用」前提下高度概率唯一;一旦时钟回拨或 workId 重复,就可能重复。下面给出“为什么不能 100 %” 和 “生产级优化方案”。


一、雪花 ID 结构(Snowflake-64bit)

 0 符号位  |  41bit 时间戳(毫秒)  |  10bit workId  |  12bit 序列号
  • 时间精度 1 ms → 69 年范围
  • 10 bit workId → 最多 1024 个节点
  • 12 bit 序列号 → 每节点每毫秒 4096 个 ID

二、绝对唯一的 3 个天敌

  1. 时钟回拨(NTP、人工改时间)
    同一毫秒时间被重复使用 → 时间戳相同 + workId 相同 + 序列号归零 → ID 重复。
  2. workId 重复(容器重启、K8s Pod IP 复用)**
    两台机被分配相同 workId,同一毫秒序列号空间完全重叠。
  3. 序列号溢出(>4096/ms)**
    单节点突刺 QPS > 4 k/ms 会等待或抛异常,若直接归零也重复。

三、生产级优化(“让概率降到宇宙级别”)

优化点做法效果
① 时钟回拨拦截发现回拨 ≤5 ms 就 spin 等待;>5 ms 抛出异常并报警消除 99 % 回拨重复
② 时钟回拨容忍记录 lastTimestamp,回拨时改用 lastTimestamp+1 并降低序列号位(牺牲吞吐换安全)极端场景也绝不重复
③ workId 自动分配启动时向 ZooKeeper / Redis 申请唯一 workId(持久顺序节点或分布式锁),进程退出即释放避免人工配置重复
④ 序列号溢出保护序列号 == 4095 时 循环等待下一毫秒,绝不回卷到 0杜绝溢出重复
⑤ 延长长度把 64 bit 扩展到 128 bit:64 bit 时间 + 48 bit workId + 16 bit 序列号,单节点每毫秒 65 k,可用 8 万年量级无限放大
⑥ 双缓冲 + 无锁使用 RingBuffer 预填充 ID,CAS 更新序列号,百万级 QPS 无 GC性能 & 安全双赢

开源增强版示例(百度 UidGenerator、美团 Leaf-Snowflake)都集成以上 ①②③④。


四、最简“防御式”代码片段

java
public class SafeSnowflake {
    private long lastTs = -1L;
    private long sequence = 0L;
    private final long workId;

    public synchronized long nextId() {
        long curr = System.currentTimeMillis();
        if (curr < lastTs) {           // 时钟回拨
            throw new ClockBackwardsException(curr, lastTs);
        }
        if (curr == lastTs) {
            sequence = (sequence + 1) & 4095;
            if (sequence == 0) {       // 本毫秒用完,等待
                while (curr <= lastTs) curr = System.currentTimeMillis();
            }
        } else {
            sequence = 0;
        }
        lastTs = curr;
        return (curr - EPOCH) << 22 | workId << 12 | sequence;
    }
}

五、结论 & 面试金句

“雪花算法在 时钟不回拨、workId 不冲突、序列号不溢出 前提下可认为‘全局唯一’;
生产环境必须加 回拨检测 + workId 自动分配 + 溢出自旋,或直接使用 Leaf-Snowflake 等增强实现,才能把重复概率压到‘宇宙规模’。”