雪花算法原理

前言

雪花算法是推特开源的分布式ID生成算法,用于在不同的机器上生成唯一的ID的算法,该算法生成一个64bit的数字作为分布式ID,保证这个ID自增并且全局唯一
第1位:不使用,一直是0;
第2~42位:表示时间戳,共41位,单位是毫秒,可以支撑大约69年;
第43~47位:表示机房ID,共5位,最多可容纳1024个机房;
第48~52位:表示机器ID,共5位,最多可容纳1024台机器;
第53~64位:表示序列号,共12位,表示同一毫秒内产生的ID,单台机器每毫秒最多可生成4096个订单ID;

一、实现

import java.util.Date;

/**
 * 雪花算法:基于时间戳生成自增ID
 */
public class Snowflake {

    // 2023-2-21 11:03:27作为系统上线时间
    private static final long START_TIMESTAMP = 1676948607869L;
    //  时间戳占用位数
    private static final long TIMESTAMP_BIT = 41L; // 41 bit 69年
    private static final long MAX_TIMESTAMP = ~(-1L << TIMESTAMP_BIT);
    //  机房占用位数
    private static final long MACHINE_ROOM_BIT = 5L; // 5 bit
    private static final long MAX_MACHINE_ROOM = ~(-1L << MACHINE_ROOM_BIT); // 11111
    //  机器占用位数
    private static final long MACHINE_BIT = 5L; // 5 bit
    private static final long MAX_MACHINE = ~(-1L << MACHINE_BIT); // 11111
    //  序列号占用位数
    private static final long SERIAL_BIT = 12L; // 12 bit
    private static final long MAX_SERIAL = ~(-1L << SERIAL_BIT); // 1111 1111 1111

    private static long curSerial = -1L;
    private static long lastTimestamp = 0L;

    /**
     * 生成id
     * @param machineRoomId 机房id
     * @param machineId 机器id
     * @return id
     * @throws Exception 空
     */
    public static synchronized long generateId(long machineRoomId, long machineId) throws Exception{
        if (machineRoomId > MAX_MACHINE_ROOM || machineId > MAX_MACHINE) {
            throw new Exception("机房号或机器号已经满额分配");
        }
        long curTimestamp = System.currentTimeMillis();
        if (curTimestamp < lastTimestamp) {
            throw new Exception("发生时间回拨: 回拨" + (curTimestamp - lastTimestamp) + "s");
        }
        //  超过时间戳使用位数
        if (curTimestamp - lastTimestamp > MAX_TIMESTAMP){
            throw new Exception("时间戳满额分配,已到达使用年限(69年)");
        }
        //  如果同时请求生成id
        if (curTimestamp == lastTimestamp) {
            curSerial++;
            if (curSerial > MAX_SERIAL) {
                throw new Exception("序列号已经满额分配");
            }
        //  如果是全新时间请求生成id
        } else {
            curSerial = 0L;
        }
        //  记录时间戳
        lastTimestamp = curTimestamp;
        return ((curTimestamp - START_TIMESTAMP) << (MACHINE_ROOM_BIT + MACHINE_BIT + SERIAL_BIT)) |
                (machineRoomId << (MACHINE_BIT + SERIAL_BIT)) |
                (machineId << SERIAL_BIT) |
                curSerial;
    }

    /**
     * 获取生成时间
     * @param snowflakeId 该雪花算法生成的id
     * @return 生成时间
     */
    public static Date toDate(long snowflakeId) {
        long timestampStep = MAX_TIMESTAMP << (MACHINE_ROOM_BIT + MACHINE_BIT + SERIAL_BIT) & snowflakeId;
        timestampStep >>>= MACHINE_ROOM_BIT + MACHINE_BIT + SERIAL_BIT;
        return new Date(START_TIMESTAMP + timestampStep);
    }

    /**
     * 获取机房id
     * @param snowflakeId 该雪花算法生成的id
     * @return 机房id
     */
    public static long toMachineRoomId(long snowflakeId) {
        long machineRoomId = MAX_MACHINE_ROOM << (MACHINE_BIT + SERIAL_BIT) & snowflakeId;
        return machineRoomId >>> (MACHINE_BIT + SERIAL_BIT);
    }

    /**
     * 获取机器id
     * @param snowflakeId 该雪花算法生成的id
     * @return 机器id
     */
    public static long toMachineId(long snowflakeId) {
        long machineId = MAX_MACHINE_ROOM << SERIAL_BIT & snowflakeId;
        return machineId >>> SERIAL_BIT;
    }
}

二、相关问题及解决办法

  • 参数满额:分配达到最大值,还要继续分配?
    • 记录前一个参数的最大值,当前参数满额时,使前一个参数值自增,当前参数则重新开始自增
    • 在算法书写时规划好各个参数的范围,随场景需要增大满额参数的占用位,缩小其他参数的占用位
  • 时钟回拨问题:当前时间小于历史最大生成时间,可能产生重复ID?
    • 生成时记录时间和序列号等信息,对当前时间下的序列号增大生成避免,但是其自增性会被破坏
    • 短周期生成场景(本文代码设置的是69年的时间周期),可考虑多时钟,将时间戳截断成两个时间进行生成,但是其自增性会被破坏
    • 记录历史最大生成时间,对历史最大生成时间自增进行生成
    • 延迟等待自增
    • 自定义时间戳,但时间戳的意义可能丢失
  • 最后,不管啥问题,抛错处理(谁爱管谁管(¬︿̫̿¬☆))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值