手撸一个简单的雪花算法生成自增id

13 篇文章 0 订阅

简介

雪花算法(Snowflake)是为了解决分布式系统中生成全局唯一ID的需求而设计的。在分布式系统中,如果多个节点同时生成ID,传统的自增ID或随机数ID可能会导致ID冲突,因此需要一种算法来确保在不同节点、不同时间生成的ID都是唯一的。

雪花算法通过将ID分为不同的部分,包括时间戳、数据中心ID、工作节点ID和序列号,来保证生成的ID的唯一性。每个部分的位数可以根据实际需求进行调整,以平衡ID的长度和唯一性要求。

雪花算法的应用场景包括但不限于以下情况:

  • 分布式系统中需要生成全局唯一ID的场景,如分布式数据库、分布式缓存、消息队列等。
  • 分布式系统中需要对大量数据进行唯一标识的场景,如日志分析系统、用户行为跟踪系统等。
  • 分布式系统中需要对事件进行排序或时间戳的场景,如分布式事务、分布式锁等。

通过使用雪花算法,可以在分布式系统中生成全局唯一的ID,避免了ID冲突的问题,并且ID的生成是有序的,可以根据时间戳进行排序。这使得雪花算法成为了一种常用的分布式ID生成方案。

具体代码实现

public class SnowflakeIdGenerator {
    private static final long EPOCH = 1609459200000L; // 起始时间戳,这里设置为 2021-01-01 00:00:00 的时间戳
    private static final long WORKER_ID_BITS = 5L; // 工作节点ID所占的位数
    private static final long DATA_CENTER_ID_BITS = 5L; // 数据中心ID所占的位数
    private static final long SEQUENCE_BITS = 12L; // 序列号所占的位数
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); // 工作节点ID的最大值
    private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS); // 数据中心ID的最大值
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; // 工作节点ID的位移量
    private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; // 数据中心ID的位移量
    private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS; // 时间戳的位移量
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS); // 序列号的掩码,用于获取序列号部分的值

    private final long workerId; // 工作节点ID
    private final long dataCenterId; // 数据中心ID
    private long sequence = 0L; // 序列号
    private long lastTimestamp = -1L; // 上一次生成ID的时间戳

    /**
     * 构造函数
     *
     * @param workerId     工作节点ID
     * @param dataCenterId 数据中心ID
     */
    public SnowflakeIdGenerator(long workerId, long dataCenterId) {
        if (workerId < 0 || workerId > MAX_WORKER_ID) {
            throw new IllegalArgumentException("Worker ID must be between 0 and " + MAX_WORKER_ID);
        }
        if (dataCenterId < 0 || dataCenterId > MAX_DATA_CENTER_ID) {
            throw new IllegalArgumentException("Data Center ID must be between 0 and " + MAX_DATA_CENTER_ID);
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }

    /**
     * 生成唯一ID
     *
     * @return 唯一ID
     */
    public synchronized long generateId() {
        long timestamp = System.currentTimeMillis();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Invalid system clock");
        }

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

        lastTimestamp = timestamp;

        return ((timestamp - EPOCH) << TIMESTAMP_LEFT_SHIFT) |
                (dataCenterId << DATA_CENTER_ID_SHIFT) |
                (workerId << WORKER_ID_SHIFT) |
                sequence;
    }

    /**
     * 获取下一个毫秒的时间戳
     *
     * @param lastTimestamp 上一次生成ID的时间戳
     * @return 下一个毫秒的时间戳
     */
    private long nextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}

上述代码中,SnowflakeIdGenerator类实现了雪花算法生成唯一ID的逻辑。以下是实现的详细细节和思路:
定义了一些常量,包括时间戳的起始时间(即雪花算法的epoch),以及各个部分的位数和位移。
构造函数接收workerId和dataCenterId作为参数,并进行参数校验。

generateId方法是生成唯一ID的核心逻辑。首先获取当前时间戳,如果时间戳小于上一次生成ID的时间戳,则抛出异常,因为时间戳应该是单调递增的。如果时间戳与上一次相同,则递增序列号(sequence),并检查序列号是否溢出。如果序列号溢出,则等待下一毫秒再生成ID。如果时间戳不同,则重置序列号为0。

生成ID的公式为:((timestamp - EPOCH) << TIMESTAMP_LEFT_SHIFT) | (dataCenterId << DATA_CENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence。其中,时间戳部分左移相应的位数,然后与数据中心ID、工作节点ID和序列号进行按位或操作,得到最终的唯一ID。

nextMillis方法用于获取下一个毫秒的时间戳,如果下一个时间戳小于等于上一次的时间戳,则一直循环等待,直到获取到合适的时间戳。

这样,通过使用SnowflakeIdGenerator类的实例,你可以在分布式系统中生成全局唯一的ID。每个实例需要指定不同的workerId和dataCenterId,以确保生成的ID在整个系统中唯一。

雪花id存在的问题

时钟回溯是指系统时钟发生了向后调整(回溯)的情况,即当前时间戳比上一次生成ID的时间戳要小。时钟回溯可能会导致雪花算法生成的ID出现重复或无序的情况,因为雪花算法依赖于时间戳的递增来保证ID的唯一性和有序性。

时钟回溯可能发生在以下情况下:

  • 手动调整系统时钟:管理员或系统维护人员手动调整了系统时钟,将时间设置为过去的某个时间点。
    时钟同步问题:在分布式系统中,不同节点的时钟可能存在微小的差异。当一个节点的时钟比其他节点的时钟快了一段时间时,如果发生时钟同步操作,可能会导致该节点的时钟回溯。
  • 时钟回溯对雪花算法的影响取决于具体的实现方式。一般情况下,雪花算法会检测到时钟回溯并抛出异常,以避免生成重复的ID。常见的处理方式包括抛出异常、等待时钟追上、记录异常等。

处理时钟回溯的方法有以下几种:

  • 抛出异常:当检测到时钟回溯时,可以抛出异常,让应用程序捕获并处理该异常。这样可以防止生成重复的ID,但需要应用程序处理异常情况。
  • 等待时钟追上:当检测到时钟回溯时,可以等待时钟追上当前时间,然后再生成ID。这样可以保证ID的有序性,但会造成一定的延迟。
  • 记录异常:当检测到时钟回溯时,可以记录异常情况,以便后续分析和处理。这种方式可以在一定程度上保证ID的唯一性和有序性,但需要额外的处理逻辑。
  • 处理时钟回溯是使用雪花算法时需要考虑的重要问题之一。根据具体的应用场景和需求,可以选择合适的处理方式来处理时钟回溯带来的影响。
  • 25
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

澄风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值