先来看看雪花算法生成的唯一ID的结构:
如上图所示,雪花算法生成的ID一共64bit。共分为4个组成部分来保证唯一性,分别是:
下面通过Hutool提供的雪花算法工具类源码来学习一下:
public class Snowflake implements Serializable { private static final long serialVersionUID = 1L; /** 起始时间戳 */ private final long twepoch; /** 机器ID的位数 */ private final long workerIdBits; /** 数据中心ID的位数 */ private final long dataCenterIdBits; /** 机器ID的最大值 */ private final long maxWorkerId; /** 数据中心ID的最大值 */ private final long maxDataCenterId; /** 序列号位数 */ private final long sequenceBits; /** 机器ID偏移量 */ private final long workerIdShift; /** 数据中心ID偏移量 */ private final long dataCenterIdShift; /** 时间戳偏移量 */ private final long timestampLeftShift; /** 序列码,用来计算序列号 */ private final long sequenceMask; /** 机器ID */ private final long workerId; /** 数据中心ID */ private final long dataCenterId; /** 是否使用Hutool提供的系统时钟 */ private final boolean useSystemClock; /** 序列号 */ private long sequence; /** 上一次生成ID的时间戳 */ private long lastTimestamp; public Snowflake(long workerId, long dataCenterId) { // 默认不使用Hutool提供的时间戳 this(workerId, dataCenterId, false); } public Snowflake(long workerId, long dataCenterId, boolean isUseSystemClock) { // 默认起始时间戳 2010-11-04 09:42:54 this((Date)null, workerId, dataCenterId, isUseSystemClock); } public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) { // 默认机器ID位数 5,同一数据中心可部署的机器节点为 2的5次方-1 this.workerIdBits = 5L; // 默认数据中心ID位数为 5,可部署的数据中心为 2的5次方-1 this.dataCenterIdBits = 5L; // 默认最大的机器ID为 31 this.maxWorkerId = 31L; // 默认最大的数据中心ID为 31 this.maxDataCenterId = 31L; // 默认序列号位数为 12 this.sequenceBits = 12L; // 机器ID的偏移量为 12,即序列号的位数 this.workerIdShift = 12L; // 数据中心的偏移量为 17,即机器ID的偏移量 + 机器ID的位数 this.dataCenterIdShift = 17L; // 时间戳的偏移量为 22,即数据中心的偏移量 + 数据中心的位数 this.timestampLeftShift = 22L; // 序列码默认为 2的12次方-1 = 4095,用来计算序列号,即同一机器每毫秒可生成4095个唯一ID this.sequenceMask = 4095L; // 默认序列号为 0 this.sequence = 0L; // 默认上一次生成ID的时间戳为 -1 this.lastTimestamp = -1L; if (null != epochDate) { // 得到自定义日期的时间戳作为起始时间戳 this.twepoch = epochDate.getTime(); } else { // 默认的起始时间戳 2010-11-04 09:42:54 this.twepoch = 1288834974657L; } // 校验机器ID的大小和数据中心ID的大小,[0,31] if (workerId <= 31L && workerId >= 0L) { if (dataCenterId <= 31L && dataCenterId >= 0L) { this.workerId = workerId; this.dataCenterId = dataCenterId; this.useSystemClock = isUseSystemClock; } else { throw new IllegalArgumentException(StrUtil.format("datacenter Id can't be greater than {} or less than 0", new Object[]{31L})); } } else { throw new IllegalArgumentException(StrUtil.format("worker Id can't be greater than {} or less than 0", new Object[]{31L})); } } /** * 核心方法:生成唯一ID * 加锁,保证线程安全 */ public synchronized long nextId() { // 获取当前时间戳 long timestamp = this.genTime(); // 如果当前时间戳小于上次生成ID的时间戳 if (timestamp < this.lastTimestamp) { // 当前时间戳与上次生成ID的时间戳差值 超过 2秒,拒绝生成ID,提示:时钟向后移动 if (this.lastTimestamp - timestamp >= 2000L) { throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", new Object[]{this.lastTimestamp - timestamp})); } // 将上次生成ID的时间戳 赋值给 当前时间戳 timestamp = this.lastTimestamp; } // 如果当前时间戳 等于 上次生成ID的时间戳 if (timestamp == this.lastTimestamp) { // 首先将序列号递增 this.sequence = this.sequence + 1L & 4095L; // 如果序列号为 0,即序列号递增后达到最大值,重新从 0开始 if (this.sequence == 0L) { // 此时无法使用序列号递增保证唯一性,则将时间戳推移到下一毫秒 timestamp = this.tilNextMillis(this.lastTimestamp); } } else { // 如果当前时间戳 不等于 上次生成ID的时间戳,则序列号从 0 开始 this.sequence = 0L; } // 将本次时间戳 赋值为 最后一次生成ID的时间戳变量 this.lastTimestamp = timestamp; // 将各个组成部分加起来,组成一个唯一ID,使用效率最高的 二进制位运算,或运算类似于十进制的加法 // 公式:(当前时间戳 - 起始时间戳)左移 时间戳的偏移量 // | 数据中心ID 左移 数据中心ID的偏移量 // | 机器ID 左移 机器ID的偏移量 // | 序列号 return timestamp - this.twepoch << 22 | this.dataCenterId << 17 | this.workerId << 12 | this.sequence; } public String nextIdStr() { return Long.toString(this.nextId()); } /** * 得到下一毫秒的时间戳 */ private long tilNextMillis(long lastTimestamp) { long timestamp; // 类似于CAS算法,循环直到下一毫秒 for(timestamp = this.genTime(); timestamp == lastTimestamp; timestamp = this.genTime()) { } // 如果计算得到的下一毫秒的时间戳 反而比 传入的参数还小了,则发生了时钟回退,抛出异常 if (timestamp < lastTimestamp) { throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", new Object[]{lastTimestamp - timestamp})); } else { // 返回下一毫秒的时间戳 return timestamp; } } /** * 获取当前时间戳 */ private long genTime() { // 默认使用 System.currentTimeMillis() 生成时间戳 return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis(); } }