/ 20浏览

Java 生成雪花算法工具类

image

Snowflake 是 Twitter 开发的一种分布式唯一 ID 生成策略,其核心思想是利用一个 64 位的长整型数(Java 中的 long 类型)来表示唯一 ID。这个 64 位的长整型数由以下几部分组成:

  1. 第 1 位是符号位,由于是正数,所以这一位是固定的 0。
  2. 接下来的 41 位表示时间戳,单位是毫秒,41 位可以表示的数值范围是 2^41 - 1​,即可以生成 2^41​ 个不同的 ID,时间戳的上限是 1970-01-01 00:00:00​ 之后的 69​ 年。
  3. 接下来的 10 位表示工作机器 ID,可以部署在 2^10​ 个节点上,即最多支持 1024 个节点。
  4. 最后 12 位是序列号,用来记录同一个毫秒内产生的不同 ID,12 位可以支持每个节点每毫秒生成 2^12​ 个 ID,即 4096 个。

以下是一个简单的 Java 实现示例:

public class SnowflakeIdWorker {
    // 开始时间戳 (2023-01-01)
    private final long twepoch = 1672444800000L;
  
    // 机器id所占的位数
    private final long workerIdBits = 5L;
    // 支持的最大机器id,结果是31
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 序列在id中占的位数
    private final long sequenceBits = 12L;
    // 机器ID向左移12位
    private final long workerIdShift = sequenceBits;
    // 时间截向左移17位(5+12)
    private final long timestampLeftShift = sequenceBits + workerIdBits;
    // 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
  
    // 工作机器ID(0~31)
    private long workerId;
    // 毫秒内序列(0~4095)
    private long sequence = 0L;
    // 上次生成ID的时间截
    private long lastTimestamp = -1L;

    public SnowflakeIdWorker(long workerId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        this.workerId = workerId;
    }

    /**
     * 生成ID
     *
     * @return id
     */
    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) {
            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) {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0);
        for (int i = 0; i < 1000; i++) {
            long id = idWorker.nextId();
            System.out.println(Long.toBinaryString(id));
            System.out.println(id);
        }
    }
}

这个实现是一个基础版本,实际应用中可能需要根据具体需求进行调整和优化。例如,可以添加线程安全的控制,或者对时间回拨的处理进行更细致的控制。此外,还可以根据实际的部署环境调整时间戳的起始点,以避免和旧的 ID 冲突。