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

文章介绍了Twitter的雪花算法,这是一种用于生成全局唯一ID的分布式算法。它通过时间戳、机器ID和序列号确保ID的唯一性和有序性。文章还提到了算法的优缺点,如依赖时钟、受限于机器数量和性能,并提供了Java实现雪花算法的示例代码,展示了如何在多数据中心和机器环境下生成ID。最后,指出了在实际应用中需要注意的问题,如时间回拨和重复ID的风险。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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,雪花算法 ID32120788563922944
数据中心2,雪花算法 ID32120788568248320
数据中心3,雪花算法 ID32120788568379392
数据中心1,雪花算法 ID32120788568117248
数据中心2,雪花算法 ID32120788568248321
数据中心3,雪花算法 ID32120788568379393
数据中心1,雪花算法 ID32120788568117249
数据中心2,雪花算法 ID32120788568248322
数据中心3,雪花算法 ID32120788568379394
数据中心1,雪花算法 ID32120788568117250
数据中心2,雪花算法 ID32120788568248323
数据中心3,雪花算法 ID32120788568379395
数据中心1,雪花算法 ID32120788572311552
数据中心2,雪花算法 ID32120788572442624
数据中心3,雪花算法 ID32120788572573696
数据中心1,雪花算法 ID32120788572311553
数据中心2,雪花算法 ID32120788572442625
数据中心3,雪花算法 ID32120788572573697
数据中心1,雪花算法 ID32120788572311554
数据中心2,雪花算法 ID32120788572442626
数据中心3,雪花算法 ID32120788572573698
数据中心1,雪花算法 ID32120788572311555
数据中心2,雪花算法 ID32120788572442627
数据中心3,雪花算法 ID32120788572573699
数据中心1,雪花算法 ID32120788572311556
数据中心2,雪花算法 ID32120788572442628
数据中心3,雪花算法 ID32120788576768000
数据中心1,雪花算法 ID32120788576505856

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

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

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

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

没有最好的,只有最合适的

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值