分布式雪花算法

简介

在分布式系统中生成全局唯一的ID是一个很常见的需求。雪花算法(Snowflake)是Twitter开源的一个生成全局唯一ID的算法,能够满足分布式系统中生成全局唯一ID的需求。

雪花算法的核心思想是通过利用64位的二进制数据,将其分为不同的部分来生成全局唯一ID。具体来说,雪花算法生成的ID包含一个 41 位的时间戳(毫秒级),10 位的机器ID 和 12 位的序列号。通过这种方式,即使在同一毫秒内,也能保证生成的ID全局唯一。

算法原理

64位二进制结构

雪花算法生成的ID是一个64位的二进制数据,将其分为不同的部分:

  • 第一个部分是 41 位的时间戳,用来表示生成ID的时间戳(毫秒级)。
  • 第二部分是 10 位的机器ID,用来表示机器的唯一标识。
  • 第三部分是 12 位的序列号,用来表示同一时间戳内生成的序列号。

生成ID的规则

具体生成ID的规则如下:

  • 时间戳部分:41 位的时间戳,从系统当前时间开始计算,可以支持 69 年的使用。
  • 机器ID部分:10 位的机器ID,可以通过配置来指定每台机器的唯一标识。
  • 序列号部分:12 位的序列号,在同一时间戳内生成多个ID时递增。

代码示例

下面是一个使用Java语言实现的分布式雪花算法的示例代码:

/**
 * 雪花算法生成全局唯一ID
 */
public class SnowflakeIdGenerator {

    private final long twepoch = 1288834974657L;
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private final long sequenceBits = 12L;
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long workerId;
    private long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(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;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        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();
    }

    public static void main(String[] args) {
        SnowflakeIdGenerator idWorker = new SnowflakeIdGenerator(1, 1);
        for (int i = 0; i < 10; i++) {
            long id