JAVA:分布式雪花算法(Snowflake)的技术指南

admin
7
2025-08-28

1、简述

在分布式系统中,唯一 ID 生成 是一个核心问题。比如订单号、用户 ID、消息 ID 等。常见方案有:

🔹 数据库自增主键(性能瓶颈、单点问题)

🔹 UUID(全局唯一但过长,且无序)

🔹 Redis/ZooKeeper 分布式自增(额外依赖)

Twitter 提出的 Snowflake(雪花算法),是一种高性能、低延迟的 分布式唯一 ID 生成算法,能够在分布式场景下高效生成 趋势递增 的唯一 ID。

image-mjph.png


2、雪花算法的 ID 结构

一个 64 位的 long 型 ID,通常分配如下(以经典实现为例):

| 符号位(1) | 时间戳(41) | 数据中心ID(5) | 机器ID(5) | 序列号(12) |

🔹 符号位(1位):固定为 0,不使用。

🔹 时间戳(41位):当前时间与自定义起始时间的差值,支持约 69 年。

🔹 数据中心ID(5位):可支持 32 个数据中心。

🔹 机器ID(5位):可支持每个数据中心 32 台机器。

🔹 序列号(12位):每毫秒可生成 4096 个 ID。

image-qmhz.png


3、雪花算法的特点

高性能:本地生成,无需远程调用。
高可用:无中心化依赖。
趋势递增:基于时间戳生成,ID 大致有序。
分布式唯一性:通过数据中心 ID + 机器 ID 区分。

⚠️ 不足

🔹 时间回拨问题:若系统时间被回调,可能生成重复 ID。

🔹 数据中心 ID、机器 ID 需要合理分配。


4、实践样例

4.1 定义 SnowflakeIdWorker

public class SnowflakeIdWorker {

    // ================== 常量 ==================
    private final long twepoch = 1609459200000L; // 自定义起始时间 2021-01-01

    private final long workerIdBits = 5L;       // 机器ID位数
    private final long datacenterIdBits = 5L;   // 数据中心ID位数
    private final long sequenceBits = 12L;      // 序列号位数

    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);       // 最大机器ID
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 最大数据中心ID
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);      // 序列掩码

    private final long workerIdShift = sequenceBits;                           // 机器ID左移位数
    private final long datacenterIdShift = sequenceBits + workerIdBits;        // 数据中心ID左移位数
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 时间戳左移位数

    // ================== 成员变量 ==================
    private long workerId;        // 机器ID
    private long datacenterId;    // 数据中心ID
    private long sequence = 0L;   // 毫秒内序列
    private long lastTimestamp = -1L; // 上次生成ID的时间戳

    // ================== 构造函数 ==================
    public SnowflakeIdWorker(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 ==================
    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; // 不同毫秒内,序列号置为0
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }
}

4.2 测试 Snowflake ID 生成

public class SnowflakeTest {
    public static void main(String[] args) {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 1);

        for (int i = 0; i < 10; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }
    }
}

4.3 运行结果示例

144116287064064000
144116287064064001
144116287064064002
144116287064064003
144116287064064004
...

这些 ID 是 唯一且大致有序 的 long 型数字。


5、雪花算法在实际系统中的应用

🔹 订单号生成:保证唯一性和趋势递增。

🔹 日志追踪 ID:用于分布式链路追踪。

🔹 消息队列 ID:保证消息唯一性。

🔹 数据库主键:避免数据库自增的单点瓶颈。


6、总结

🔹 雪花算法通过 时间戳 + 数据中心ID + 机器ID + 序列号 实现分布式唯一 ID 生成;

🔹 优点:高性能、去中心化、趋势递增;

🔹 缺点:依赖时间,需防止时钟回拨问题;

🔹 在订单、日志、消息等场景广泛应用。

动物装饰