实现高性能分布式 ID 生成器:Java 雪花算法详解

1、简介

雪花算法(Snowflake)是 Twitter 开源的分布式 ID 生成算法,可以生成不重复的、有序的、可自增的 64 位 ID,适用于分布式系统中的 ID 生成需求。

雪花算法的核心思想是将一个 64 位的 ID 按照一定的规则进行拆分,其中 41 位作为时间戳,10 位作为机器 ID,12 位作为序列号,保证了生成的 ID 全局唯一、有序、可自增。

雪花算法的 ID 由以下几个部分组成:

  • 符号位:1 个 bit,始终为 0,用于区分正数和负数。

  • 时间戳:41 个 bit,精确到毫秒级别。使用当前时间减去一个固定的开始时间,可以得到一个时间差值。由于时间戳占用了 41 个 bit,最大可表示的时间为 2^41 / (1000 * 60 * 60 * 24 * 365) = 69 年左右。

  • 数据中心 ID:5 个 bit,用于区分不同的数据中心。如果没有多个数据中心,可以将其设置为 0。

  • 机器 ID:5 个 bit,用于区分同一数据中心内不同的机器。同样地,如果没有多台机器,可以将其设置为 0。

  • 序列号:12 个 bit,用于区分同一毫秒内生成的不同 ID。由于序列号只有 12 个 bit,最大可表示的序列号为 2^12 - 1 = 4095。如果在同一毫秒内生成的序列号超过了 4095,需要等到下一毫秒再生成新的 ID。

综上所述,一个雪花算法生成的 ID 长度为 64 bit,可以保证在分布式系统中生成唯一的 ID。

2、雪花算法优缺点

2.1、雪花算法的优点包括:

  • 高效性:雪花算法生成 ID 的速度非常快,可以满足高并发场景下的需求。

  • 唯一性:通过使用时间戳和机器 ID 等信息来生成 ID,保证了生成的 ID 的唯一性。

  • 易于使用:使用雪花算法生成 ID 的代码相对简单,易于集成到现有系统中。

2.2、雪花算法的缺点包括:

  • 依赖时钟:雪花算法生成 ID 的唯一性和正确性依赖于时钟的正确性。如果机器的时钟出现问题,可能会导致生成的 ID 不唯一或不正确。

  • 受限于机器数量:雪花算法的唯一性也受限于机器数量,如果集群中的机器数量超过了算法中机器 ID 的范围,可能会导致生成的 ID 不唯一。

  • 受限于机器性能:如果机器性能较差,可能会导致生成 ID 的速度变慢,无法满足高并发的需求。

3、Java 实现雪花算法

以下是 Java 实现雪花算法的示例代码:

/**
 * <h1>Java 雪花算法</h1>
 * Created by woniu
 */
public class SnowFlake {
    
    
    // 起始的时间戳,这个时间戳可以是你的系统初始时间,一般取当前时间戳
    private final static long START_TIMESTAMP = 1672502400000L; // 2023-01-01 00:00:00

    // 每一部分占用的位数,可以根据自己的需求进行调整,这里是按照默认的占位数进行分配
    private final static long SEQUENCE_BIT = 12; // 序列号占用的位数
    private final static long MACHINE_BIT = 5; // 机器标识占用的位数
    private final static long DATA_CENTER_BIT = 5; // 数据中心占用的位数

    // 每一部分的最大值,可以根据占用的位数进行计算得到
    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
    private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
    private final static long MAX_DATA_CENTER_NUM = ~(-1L << DATA_CENTER_BIT);

    // 每一部分向左的位移,计算出来的值是为了后面生成 ID 做准备
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;

    private long dataCenterId; // 数据中心 ID
    private long machineId; // 机器 ID
    private long sequence = 0L; // 序列号
    private long lastTimeStamp = -1L; // 上一次时间戳

    /**
     * <h2>构造方法</h2>
     * @param dataCenterId 数据中心 ID
     * @param machineId 机器 ID
     * */
    public SnowFlake(long dataCenterId, long machineId) {
    
    
        if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
    
    
            throw new IllegalArgumentException("数据中心标识不能大于等于 " + MAX_DATA_CENTER_NUM + " 或小于 0");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
    
    
            throw new IllegalArgumentException("机器标识不能大于等于 " + MAX_MACHINE_NUM + " 或小于 0");
        }
        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
    }

    /**
     * <h2>雪花算法核心方法</h2>
     * 通过调用 nextId() 方法,让当前这台机器上的 snowflake 算法程序生成一个全局唯一的 id
     * */
    public synchronized long nextId() {
    
    
        // 获取系统当前时间戳
        long currentTimeStamp = getSystemCurrentTimeMillis();
        if (currentTimeStamp < lastTimeStamp) {
    
    
            throw new RuntimeException("时钟向后移动,拒绝生成雪花算法ID");
        }

        if (currentTimeStamp == lastTimeStamp) {
    
    
            // 当前毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 序列号超出范围,需要等待下一毫秒
            if (sequence == 0L) {
    
    
                // 获取下一毫秒
                currentTimeStamp = getNextMill(lastTimeStamp);
            }
        } else {
    
    
            // 不同毫秒内,序列号置为 0
            sequence = 0L;
        }

        lastTimeStamp = currentTimeStamp;

        // 使用位运算生成最终的 ID
        return (currentTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT
                | dataCenterId << DATA_CENTER_LEFT
                | machineId << MACHINE_LEFT
                | sequence;
    }

    /**
     * <h2>获取系统当前时间戳</h2>
     * @return 当前时间(毫秒)
     */
    private long getSystemCurrentTimeMillis() {
    
    
        return System.currentTimeMillis();
    }

    /**
     * <h2>获取下一毫秒</h2>
     * 当某一毫秒的时间,产生的 id 数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生 ID
     * @param lastTimestamp 上次生成 ID 的时间截
     * @return 当前时间戳
     */
    private long getNextMill(long lastTimestamp) {
    
    
        long timeMillis = getSystemCurrentTimeMillis();
        while(timeMillis <= lastTimestamp){
    
    
            timeMillis = getSystemCurrentTimeMillis();
        }
        return timeMillis;
    }

    /**
     * <h2>测试类</h2>
     */
    public static void main(String[] args) {
    
    
        SnowFlake worker1 = new SnowFlake(1,1);
        SnowFlake worker2 = new SnowFlake(2,1);
        SnowFlake worker3 = new SnowFlake(3,1);
        for (int i = 0; i < 30; i++){
    
    
            System.out.println("数据中心1,雪花算法 ID:" + worker1.nextId());
            System.out.println("数据中心2,雪花算法 ID:" + worker2.nextId());
            System.out.println("数据中心3,雪花算法 ID:" + worker3.nextId());
        }
    }

运行结果如下:

数据中心1,雪花算法 ID:32120788563922944
数据中心2,雪花算法 ID:32120788568248320
数据中心3,雪花算法 ID:32120788568379392
数据中心1,雪花算法 ID:32120788568117248
数据中心2,雪花算法 ID:32120788568248321
数据中心3,雪花算法 ID:32120788568379393
数据中心1,雪花算法 ID:32120788568117249
数据中心2,雪花算法 ID:32120788568248322
数据中心3,雪花算法 ID:32120788568379394
数据中心1,雪花算法 ID:32120788568117250
数据中心2,雪花算法 ID:32120788568248323
数据中心3,雪花算法 ID:32120788568379395
数据中心1,雪花算法 ID:32120788572311552
数据中心2,雪花算法 ID:32120788572442624
数据中心3,雪花算法 ID:32120788572573696
数据中心1,雪花算法 ID:32120788572311553
数据中心2,雪花算法 ID:32120788572442625
数据中心3,雪花算法 ID:32120788572573697
数据中心1,雪花算法 ID:32120788572311554
数据中心2,雪花算法 ID:32120788572442626
数据中心3,雪花算法 ID:32120788572573698
数据中心1,雪花算法 ID:32120788572311555
数据中心2,雪花算法 ID:32120788572442627
数据中心3,雪花算法 ID:32120788572573699
数据中心1,雪花算法 ID:32120788572311556
数据中心2,雪花算法 ID:32120788572442628
数据中心3,雪花算法 ID:32120788576768000
数据中心1,雪花算法 ID:32120788576505856

相比于传统的自增序列或 UUID,雪花算法可以在分布式系统中保证 ID 的唯一性,同时不依赖于中心节点的生成,而是通过机器 ID 和时间戳的组合实现分布式的 ID 生成。这样可以避免单点故障和性能瓶颈,提高系统的可伸缩性和性能。

雪花算法的实现方式也比较简单,只需要在每台机器上部署一个单独的 ID 生成器,通过配置不同的机器 ID 和数据中心 ID,就可以保证全局唯一性。目前,雪花算法已经广泛应用于各种分布式系统中,如 Hadoop、Zookeeper、Kafka、Elasticsearch 等。

需要注意的是,雪花算法并不是绝对安全的,由于时间戳精度的限制和机器 ID 的分配方式,可能会存在时间回拨、重复机器 ID 等问题。因此,在实际应用中,需要根据具体场景进行适当的调整和优化,以保证 ID 的全局唯一性和正确性。

本文教程到此结束,祝愿小伙伴们在编程之旅中能够愉快地探索、学习、成长!

猜你喜欢

转载自blog.csdn.net/u011374856/article/details/129857402