雪花ID工具类+详细注释+异常处理思路

今天在业务会议上针对数据量问题进行了讨论,在大数据高并发进行存储时Redis生成ID会重复,导致数据库存储失败,再加上是比较重要的数据,所以采用雪花ID进行,任务落在了我的身上,参考了市面上雪花ID生成的逻辑,写了个雪花ID工具类,主要包括获取一个雪花ID和获取多个雪花ID两个方法,工具类贴在下方了,异常处理目前只提供思路,暂未实现,后期进行实现时会更细代码

public class MySnowflakeUtil {

    // 时间戳 当前时间减去设置时间 为41为时间戳差值
    private static final long START_TIMESTAMP = 1600000000000L;
    // 数据中心(机房/环境)所占ID位数
    private static final long DATACENTER_ID_BITS = 5L;
    // 机器(主机)所占ID位数
    private static final long WORKER_ID_BITS = 5L;
    // 最大数据中心ID 最大32
    private static final long MAX_DATACENTER_ID = -1L ^ (-1L << DATACENTER_ID_BITS);
    // 最大机器ID 最大32
    private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);
    // 一毫秒内同时生成的id的序号位数
    private static final long SEQUENCE_BITS = 12L;
    // 序列号最大值 一毫秒最大4095 1s最大数为 4095 * 1000
    private static final long SEQUENCE_MASK = -1L ^ (-1L << SEQUENCE_BITS);
    // 下面三个属性表明 每个属性 在最终生成ID时要如果操作原时间戳
    // 时间左移位数
    private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;
    // 数据中心ID左移位数
    private static final long DATACENTER_ID_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    // 机器ID左移位数
    private static final long WORKER_ID_LEFT_SHIFT = SEQUENCE_BITS;
    // 上次生成ID的时间戳和序列号
    private long lastTimestamp = -1L;
    // 用CAS来存储一毫秒内生成ID的最新序列号
    private AtomicLong sequence = new AtomicLong(0L);
    // 数据中心(机房/环境IP)ID
    private long datacenterId;
    // 机器(主机IP)ID
    private long workerId;
    // 已经错误次数
    private long ERROR_COUNT = 0L;
    // 备用时间
    private long STANDBY_DATE;

    // 构造方法
    public MySnowflakeUtil(long datacenterId, long workerId) {
        // 数据中心ID不能为空且大于0小于最大值
        if (Objects.isNull(datacenterId) || datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenterId can't be greater than %d or less than 0", MAX_DATACENTER_ID));
        }
        // 机器ID不能为空且大于0小于最大值
        if (Objects.isNull(workerId) || workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", MAX_WORKER_ID));
        }
        this.datacenterId = datacenterId;
        this.workerId = workerId;
    }

    /**
     * 获取一个雪花ID
     * @return 雪花ID
     */
    public synchronized long nextId() {
        // 获取当前时间戳
        long timestamp = timeGen();
        // 判断是否小于最后一次ID生成时间 考虑到获取时间时有补偿机制 当前位置不可能小于最后时间
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        // 如果当前时间等于最后一次生成ID时间
        if (lastTimestamp == timestamp) {
            // 自旋锁
            // 判断当前线程是否被更改 如果未被更改,则将当前属性值+1 并且不能超过最大值
            sequence.compareAndSet(sequence.get(), (sequence.get() + 1) & SEQUENCE_MASK);
            // 在上方代码中 Id起为0终为0 当=0时认为当前毫秒内ID以增加至0 等待下一毫秒再进行ID生成
            if (sequence.get() == 0L) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 如果当前时间和最后生成时间不在一毫秒内 则 认为是新一毫秒
            sequence.set(0L);
        }

        // 设置最后生成时间为当前时间
        lastTimestamp = timestamp;

        // 生成ID
        return ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT) |
                (datacenterId << DATACENTER_ID_LEFT_SHIFT) |
                (workerId << WORKER_ID_LEFT_SHIFT) |
                sequence.get();
    }

    /**
     * 获取多个ID 具体数量为count
     * @param count 需要生成多少个ID
     * @return
     */
    public long[] nextIds(int count) {
        // 多重考虑 内存开销,安全性,访问速度 采用数组而并非List
        long[] ids = new long[count];
        for (int i = 0; i < count; i++) {
            ids[i] = nextId();
        }
        return ids;
    }

    /**
     * 获取当前时间戳
     * @return 时间戳
     */
    // 该方法可以简化 直接返回System.currentTimeMillis(); 可以上面判断当前时间是否小于最后生成时间的位置进行逻辑处理
    protected long timeGen() {
        // 服务器当前时间
        long now = System.currentTimeMillis();
        if (now < START_TIMESTAMP || now < lastTimestamp) {
            // 当前时间小于开始时间 或者 小于上次生成时间 认为时钟回调
            // 采用补偿机制获取最后时间
            // 方法一 配置Bean 在启动时 从数据库获取最后一次ID生成时间
            // 方法二 @Value 运维人员手动设置开始时间
            // 将补偿机制获取的时间赋值给备用时间
            if (!Objects.isNull(STANDBY_DATE)) {
                // 判断备用时间是否小于 如果备用时间也有问题 则直接抛出异常
                if (STANDBY_DATE < START_TIMESTAMP || STANDBY_DATE < lastTimestamp) {
                    // 如果备用时间也有问题 根据业务需要 判定是否继续执行
                    throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", "获取时间异常"));
                }
                // 备用时间没有问题 则直接返回备用时间
                return STANDBY_DATE;
            }
        }
        return System.currentTimeMillis();
    }

    /**
     * 等待下一个毫秒 ERROR_COUNT
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值