雪花算法生成ID
Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将64-bit位分割成多个部分,每个部分代表不同的含义。而Java中64bit的整数是Long类型,所以在Java中 SnowFlake算法生成的ID就是long来存储的。
-
第1位占用1bit,其值始终是0,可看做是符号位不使用。
-
第2位开始的41位是时间戳,41-bit位可表示2^41个数,每个数代表毫秒,那么雪花算法可用的时间年限是(1L<<41)/(1000L360024*365)=69年的时间。
-
中间的10-bit位可表示机器数,即2^10=1024台机器,但是一般情况下我们不会部署这么台机器。如果我们对IDC(互联网数据中心)有需求,还可以将10-bit分5-bit给IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,具体的划分可以根据自身需求定义。
-
最后12-bit位是自增序列,可表示2^12= 4096个数。
这样的划分之后相当于在一毫秒一个数据中心的一台机器上可产生4096个有序的不重复的ID。但是我们IDC和机器数肯定不止一个,所以毫秒内能生成的有序ID数是翻倍的。
Java 代码实现
package com.royali.test;
public class SnowFlakeDistributedId {
//开始时间戳
private final long twepoch = 1420041600000L;
//机器id所占的位数
private final long workerIdBits = 5L;
//数据表示id所占的位数
private final long datacenterIdBits = 5L;
//支持的最大机器id,结果是31
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
//支持的最大数据标识id,结果是31
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
//序列在id中占的位数
private final long sequenceBits = 12L;
//机器id向左偏移12位;
private final long workerIdShift = sequenceBits;
//数据标识id向左移17位;
private final long datacenterIdShift = sequenceBits + workerIdBits;
//时间戳向左移22位(12+5+5)
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
//生成序列的掩码,这里为4095
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
//工作机器id
private long workerId;
//数据中心id
private long datacenterId;
//毫秒内序列
private long sequence = 0L;
//上次id的时间戳
private long lastTimestamp = -1L;
/**
*
* @param workerId 工作ID(0-31)
* @param datacenterId 数据中心ID (0-31)
*/
public SnowFlakeDistributedId(long workerId,long datacenterId){
if (workerId > maxWorkerId || workerId < 0){
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0){
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId(){
long timestamp = timeGen();
//如果当前时间小于上次ID生成的时间戳,说明系统时钟回退过这个时候应该报错
if (timestamp < lastTimestamp){
throw new IllegalArgumentException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp){
sequence = (sequence+1) & sequence;
//毫秒内序列溢出
if (sequence == 0){
// 阻塞到下一个毫秒,获得新的时间戳
timestamp = tillNextMillis(lastTimestamp);
}
}else {
sequence = 0L;
}
//上次生成ID的时间戳
lastTimestamp = timestamp;
//移位并通过或运算拼到一起组成64位的ID
return ((timestamp -twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
}
private long tillNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp){
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowFlakeDistributedId idWorker = new SnowFlakeDistributedId(0, 0);
for (int i = 0; i < 1000; i++) {
System.out.println(idWorker.nextId());
}
}
}
id结果展示: