java long 类型 雪花算法 雪花算法线程安全_java long 类型 雪花算法

原理

Snowflake 雪花算法,由Twitter提出并开源,可在分布式环境下用于生成唯一ID的算法。该算法生成的是一个64位的ID,故在Java下正好可以通过8字节的long类型存放。所生成的ID结构如下所示

java long 类型 雪花算法 雪花算法线程安全_时间戳_02

  • 符号位

最高位是符号位,为保证生成的ID是正数,故不使用,其值恒为0

  • 时间戳

用来记录时间戳的毫秒数。一般地,我们会选用系统上线的时间作为时间戳的相对起点,而不使用JDK默认的时间戳起点(1970-01-01 00:00:00)。41位长度的时间戳可以保证使用69年。对于一般的项目而言,这个时间长度绝对是够用了

java long 类型 雪花算法 雪花算法线程安全_序列号_03

  • 数据中心ID、机器ID

数据中心(机房)ID、机器ID一共10位,用于标识工作的计算机,在这里数据中心ID、机器ID各占5位。实际上,数据中心ID的位数、机器ID位数可根据实际情况进行调整,没有必要一定按1:1的比例分配来这10位

  • 序列号

最低的12位为序列号,可用于标识、区分同一个计算机在相同毫秒时间内的生产的ID

综上所述,Snowflake 雪花算法生成的ID不是随机的,而是按时间顺序升序排列的;且可以保证在分布式高并发环境下生成的ID不会发生重复

Java实现

这里使用Java来实现一个基于 Snowflake 雪花算法的ID生成器




/**
 
   
  
 * Snowflake 基于雪花算法的ID生成器
 
   
  
 */
 
   
  
public class SnowflakeIdGenerator {
 
   
  
 /**
 
   
  
 * ID中41位时间戳的起点 (2020-01-01 00:00:00.00)
 
   
  
 * @apiNote 一般地,选用系统上线的时间
 
   
  
 */
 
   
  
 private final long startPoint = 1577808000000L;
 
   
  
 /**
 
   
  
 * 序列号位数
 
   
  
 */
 
   
  
 private final long sequenceBits = 12L;
 
   
  
 /**
 
   
  
 * 机器ID位数
 
   
  
 */
 
   
  
 private final long workerIdBits = 5L;
 
   
  
 /**
 
   
  
 * 数据中心ID位数
 
   
  
 */
 
   
  
 private final long dataCenterIdBits = 5L;
 
   
  
 /**
 
   
  
 * 序列号最大值, 4095
 
   
  
 * @apiNote 4095 = 0xFFF,其相当于是序列号掩码
 
   
  
 */
 
   
  
 private final long sequenceMask = -1L^(-1L<<sequenceBits);
 
   
  
 /**
 
   
  
 * 机器ID最大值, 31
 
   
  
 */
 
   
  
 private final long maxWorkerId = -1L^(-1L<<workerIdBits);
 
   
  
 /**
 
   
  
 * 数据中心ID最大值, 31
 
   
  
 */
 
   
  
 private final long maxDataCenterId = -1L^(-1L<<dataCenterIdBits);
 
   
  
 /**
 
   
  
 * 机器ID左移位数, 12
 
   
  
 */
 
   
  
 private final long workerIdShift = sequenceBits;
 
   
  
 /**
 
   
  
 * 数据中心ID左移位数, 12+5
 
   
  
 */
 
   
  
 private final long dataCenterIdShift = sequenceBits + workerIdBits;
 
   
  
 /**
 
   
  
 * 时间戳左移位数, 12+5+5
 
   
  
 */
 
   
  
 private final long timeStampShift = sequenceBits + workerIdBits + dataCenterIdBits;
 
   
  
 /**
 
   
  
 * 数据中心ID, Value Range: [0,31]
 
   
  
 */
 
   
  
 private long dataCenterId;
 
   
  
 /**
 
   
  
 * 机器ID, Value Range: [0,31]
 
   
  
 */
 
   
  
 private long workerId;
 
   
  
 /**
 
   
  
 * 相同毫秒内的序列号, Value Range: [0,4095]
 
   
  
 */
 
   
  
 private long sequence = 0L;
 
   
  
 /**
 
   
  
 * 上一个生成ID的时间戳
 
   
  
 */
 
   
  
 private long lastTimeStamp = -1L;
 
   
  
 /**
 
   
  
 * 构造器
 
   
  
 * @param dataCenterId 数据中心ID
 
   
  
 * @param workerId 机器中心ID
 
   
  
 */
 
   
  
 public SnowflakeIdGenerator(Long dataCenterId, Long workerId) {
 
   
  
 if(dataCenterId==null || dataCenterId<0 || dataCenterId>maxDataCenterId
 
   
  
 || workerId==null || workerId<0 || workerId>maxWorkerId) {
 
   
  
 throw new IllegalArgumentException("输入参数错误");
 
   
  
 }
 
   
  
 this.dataCenterId = dataCenterId;
 
   
  
 this.workerId = workerId;
 
   
  
 }
 
   
  
 /**
 
   
  
 * 获取ID
 
   
  
 * @return
 
   
  
 */
 
   
  
 public synchronized long nextId() {
 
   
  
 long currentTimeStamp = System.currentTimeMillis();
 
   
  
 //当前时间小于上一次生成ID的时间戳,系统时钟被回拨
 
   
  
 if( currentTimeStamp < lastTimeStamp ) {
 
   
  
 throw new RuntimeException("系统时钟被回拨");
 
   
  
 }
 
   
  
 // 当前时间等于上一次生成ID的时间戳,则通过序列号来区分
 
   
  
 if( currentTimeStamp == lastTimeStamp ) {
 
   
  
 // 通过序列号掩码实现只取 (sequence+1) 的低12位结果,其余位全部清零
 
   
  
 sequence = (sequence + 1) & sequenceMask;
 
   
  
 if(sequence == 0) { // 该时间戳下的序列号已经溢出
 
   
  
 // 阻塞等待下一个毫秒,并获取新的时间戳
 
   
  
 currentTimeStamp = getNextMs(lastTimeStamp);
 
   
  
 }
 
   
  
 } else { // 当前时间大于上一次生成ID的时间戳,重置序列号
 
   
  
 sequence = 0;
 
   
  
 }
 
   
  
 // 更新上次时间戳信息
 
   
  
 lastTimeStamp = currentTimeStamp;
 
   
  
 // 生成此次ID
 
   
  
 long nextId = ((currentTimeStamp-startPoint) << timeStampShift)
 
   
  
 | (dataCenterId << dataCenterIdShift)
 
   
  
 | (workerId << workerIdShift)
 
   
  
 | sequence;
 
   
  
 return nextId;
 
   
  
 }
 
   
  
 /**
 
   
  
 * 阻塞等待,直到获取新的时间戳(下一个毫秒)
 
   
  
 * @param lastTimeStamp
 
   
  
 * @return
 
   
  
 */
 
   
  
 private long getNextMs(long lastTimeStamp) {
 
   
  
 long timeStamp = System.currentTimeMillis();
 
   
  
 while(timeStamp<=lastTimeStamp) {
 
   
  
 timeStamp = System.currentTimeMillis();
 
   
  
 }
 
   
  
 return timeStamp;
 
   
  
 }
 
   
  
}




上述代码比较简单,这里对其中涉及到的位运算部分作解释说明

1. 计算X位Bit能表示的最大值

计算X位Bit能表示的最大值,最简单的是 Math.pow(2,X)-1,不过还可以通过位运算来提高速度,即 -1^(-1<

java long 类型 雪花算法 雪花算法线程安全_java long 类型 雪花算法_04

Note:

在计算机的二进制下 -1 使用全1进行表示

2. 判定某个值是否大于X位Bit所能表示的最大值

判定一个数num是否大于X位Bit所能表示的最大值maxNum,可以直接用关系运算符进行比较。但是由于maxNum的低X位均为1,故我们还可以将其看作是一个掩码,用于将num中除低X位外的其余位全部清零。在num从1逐渐递增的过程中,当 num&maxNum 的结果恰好为0时,则表明num已经开始大于maxNum。这里以X等于3为例作图解说明

java long 类型 雪花算法 雪花算法线程安全_雪花算法原理_05