Java使用雪花id生成算法详解

 更新时间:2022年12月20日 09:55:27   作者:码畜c  
SnowFlake算法,是Twitter开源的分布式id生成算法,在2014年开源,开源的版本由scala编写。其核心思想就是-使用一个64bit的long型的数字作为全局唯一id

什么是雪花算法

雪花算法的本质为生成一个64位长度的具有自增性的分布式全局唯一id。在64bits中,会对不同段的位进行划分。可分为:

  • 符号段
  • 时间戳段
  • 机器码段(data center + worker)
  • 自增序列号段

位段详解

  • 第一位 : 符号位,正数为0。
  • [2, 42] : 41位时间戳位,表明id的生成时间点(完整时间戳: 起始时间戳 + 41位时间戳)。41位最多能表示的时间为: (2^41-1) / (1000 * 60 * 60 * 24 * 365) 约等为69.73年。
  • [43, 47] : 5位data center id。data center id + worker id 共10位,最多能表示1024个机器。不同机器保证机器码段的位值不同即可。
  • [48, 52] : 5位worker id。data center id + worker id 共10位,最多能表示1024个机器。不同机器保证机器码段的位值不同即可。
  • [53, 64] : 12位自增序列号,用于区分同一毫秒内生成的id。序列号范围: [0, 2^12-1],最多有2^12个,即4096个。

优点

  • 算法简单,基于内存,生成效率高
  • 支持分布式环境下的多节点服务(机器码段),秒内可生成百万个唯一id
  • 基于时间戳 与 同时间戳下自增序列号,生成的id具有自增性
  • 具有业务定制性,根据业务的不同可以对不同段的位数进行变更。比如业务持续时长不会那么久,就可以将时间戳段减少位数,补充给自增序列段,使每一毫秒能生成更多的id。

问题

依赖服务器时间。若服务器时钟回拨,可能会导致生成的id重复。可在代码中新增lastTimeMillis字段,在获取nextId时根据系统当前时间进行判断解决。

但若不进行持久化处理,服务重启后发生时钟回拨依旧会出现重复问题。

实际应用

  • mybatis plus:使用雪花算法生成id:@TableId(value = “id”, type = IdType.ID_WORKER)。id字段若不指定类型,默认使用雪花算法生成id
  • Hutool工具包:IdUtil.createSnowflake(workerId, datacenterId);

具体实现

/**
 * Created by QQ.Cong on 2022-07-22 / 9:48
 *
 * @author: CongQingquan
 * @Description: Snowflake util
 */
public class SnowflakeUtils {
    // ============================== Basic field ==============================//
    // Datacenter id
    private long datacenterId;
    // Worker id
    private long workerId;
    // Increment sequence
    private long sequence;
    // ============================== Bits ==============================//
    // Bits of datacenter id
    private long datacenterIdBits;
    // Bits of worker id
    private long workerIdBits;
    // Bits of sequence
    private long sequenceBits;
    // ============================== Largest ==============================//
    // Largest datacenter id
    private long largestDatacenterId;
    // Largest worker id
    private long largestWorkerId;
    // Largest sequence
    private long largestSequence;
    // ============================== Shift ==============================//
    // Left shift num of worker id
    private long workerIdShift;
    // Left shift num of datacenter id
    private long datacenterIdShift;
    // Left shift num of timestamp
    private long timestampShift;
    // ============================== Other ==============================//
    // Epoch
    private long epoch;
    // The timestamp that last get snowflake id
    private long lastTimestamp;
    // ============================== End ==============================//
    public SnowflakeUtils(long dataCenterId, long workerId) {
        // Default epoch: 2022-07-22 00:00:00
        this(1658419200000L, -1L, dataCenterId, workerId, 5L, 5L, 5L);
    }
    public SnowflakeUtils(long epoch, long lastTimestamp, long datacenterId, long workerId,
        long datacenterIdBits, long workerIdBits, long sequenceBits) {
        this.epoch = epoch;
        this.lastTimestamp = lastTimestamp;
        this.datacenterId = datacenterId;
        this.workerId = workerId;
        this.sequence = 0L;
        this.datacenterIdBits = datacenterIdBits;
        this.workerIdBits = workerIdBits;
        this.sequenceBits = sequenceBits;
        this.largestDatacenterId = ~(-1L << datacenterIdBits);
        this.largestWorkerId = ~(-1L << workerIdBits);
        this.largestSequence = ~(-1L << sequenceBits);
        if (datacenterId > largestDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(
                String.format("The datacenter id param can't be greater than %s or less than 0",
                    largestDatacenterId));
        }
        if (workerId > largestWorkerId || workerId < 0) {
            throw new IllegalArgumentException(
                String.format("The worker id param can't be greater than %s or less than 0",
                    largestWorkerId));
        }
        this.workerIdShift = sequenceBits;
        this.datacenterIdShift = workerIdShift + workerIdBits;
        this.timestampShift = datacenterIdShift + datacenterIdBits;
    }
    /**
     * Get snowflake id
     * @return
     */
    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();
        // 若时钟回退
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                "System clock moved backward, cannot to generate snowflake id");
        }
        // 若当前毫秒内多次生成雪花id
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & largestSequence;
            // 序列溢出
            if (sequence == 0) {
                timestamp = waitUntilNextMilli(timestamp);
            }
        }
        // 若当前毫秒内首次生成雪花id
        else {
            sequence = 0L;
        }
        // 更新获取雪花id的时间戳
        lastTimestamp = timestamp;
        // 生成雪花id (通过位或运算符进行拼接)
        return ((timestamp - epoch) << timestampShift) // 时间戳段
            | (datacenterId << datacenterIdShift) // 机器码段
            | (workerId << workerIdShift) // 机器码段
            | sequence; // 自增序列段
    }
    /**
     * Wait until next millisecond
     * @param lastTimestamp
     * @return
     */
    private long waitUntilNextMilli(long lastTimestamp) {
        long currentTimeMillis;
        do {
            currentTimeMillis = System.currentTimeMillis();
        }
        while (currentTimeMillis <= lastTimestamp);
        return currentTimeMillis;
    }
    /**
     * Get util instance
     * @param dataCenterId
     * @param workerId
     * @return
     */
    public static SnowflakeUtils getInstance(long dataCenterId, long workerId) {
        return new SnowflakeUtils(dataCenterId, workerId);
    }
}

到此这篇关于Java使用雪花id生成算法详解的文章就介绍到这了,更多相关Java雪花id生成算法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中双大括号初始化的理解与使用

    Java中双大括号初始化的理解与使用

    最近重读Java 编程思想,读到有关实例化代码块儿的内容,使我对于使用两个大括号进行初始化有了更深的理解,下面这篇文章主要给大家介绍了关于Java中双大括号初始化的理解与使用的相关资料,需要的朋友可以参考下
    2022-06-06
  • Java线程池源码的深度解析

    Java线程池源码的深度解析

    线程池的好处和使用本篇文章就不赘叙了,这篇文章主要通过线程池的源码带大家深入了解一下jdk8中线程池的实现,感兴趣的小伙伴可以了解一下
    2022-10-10
  • Tomcat ClassLoader打破双亲委派源码解析

    Tomcat ClassLoader打破双亲委派源码解析

    这篇文章主要为大家介绍了Tomcat ClassLoader打破双亲委派源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Springboot Retry组件@Recover失效问题解决方法

    Springboot Retry组件@Recover失效问题解决方法

    在使用springboot的retry模块时,你是否出现过@Recover注解失效的问题呢?不用担心,这篇文章就来告诉你解决@Recover失效的办法,需要的小伙伴可以参考一下
    2021-11-11
  • 详解java==运算符和equals()方法的区别

    详解java==运算符和equals()方法的区别

    这篇文章主要介绍了java==运算符和equals()方法的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • Kafka简单客户端编程实例

    Kafka简单客户端编程实例

    这篇文章主要为大家详细介绍了Kafka简单客户端编程实例,利用Kafka的API进行客户端编程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • 详解WebSocket+spring示例demo(已使用sockJs库)

    详解WebSocket+spring示例demo(已使用sockJs库)

    本篇文章主要介绍了WebSocket spring示例demo(已使用sockJs库),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • Java比较器实现方法项目案例

    Java比较器实现方法项目案例

    这篇文章主要介绍了Java比较器实现方法,结合具体项目案例形式分析了Java比较器相关排序操作技巧,需要的朋友可以参考下
    2019-03-03
  • java实现八皇后问题示例分享

    java实现八皇后问题示例分享

    这篇文章主要介绍了java实现八皇后问题示例,八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出
    2014-03-03
  • Java拦截器Interceptor实现原理及代码示例

    Java拦截器Interceptor实现原理及代码示例

    本文详细讲解了Java拦截器Interceptor实现原理及代码示例,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12

最新评论