ID生成器【雪花算法】
在分布式系统中,生成全剧唯一的ID,是一个特别刚需的事情。而鼎鼎有名的算法之一——来自Twitter公司的雪花算法。
利用雪花算法创建分布式ID,可以很有效的帮助我们获取到一个全局唯一、总体按时间递增的ID,合理的配置几乎不会获取到重复的ID,所以该ID是可以全局使用。
下面案例是使用41位时间+10位机器码+12位序列来展示。
基于Java版本的源码:
package com.java;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 基于雪花算法生成ID
*
* @author yuanjun
*
* Twitter_Snowflake<br>
* SnowFlake的结构如下(每部分用-分开):<br>
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。
* 41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
* 以上加起来刚好64位,为一个Long型。(1 + 41 + 10 + 12)<br>
* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
*/
public class IDCreator {
private final static int min_time_bits = 41;
private final static int default_worker_bits = 10;
private final static int default_sequence_bits = 12;
/**开始日期*/
private final static String default_epoch_date = "2022-01-01";
// 24
private final static int max_worker_sequence_bits = 64 - 1 - min_time_bits;
private final static long ts_year = 1000L * 60 * 60 * 24 * 365;
public final static int default_max_worker_id = getMaxWorkerId(default_worker_bits);
public static int getMaxWorkerId(int workerBits) {
return (1 << workerBits) - 1;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
private final SimpleDateFormat sdf_date = new SimpleDateFormat("yyyy-MM-dd");
private final SimpleDateFormat sdf_ts = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private final int workerBits;
private final int sequenceBits;
private final int timeBits;
private final long epoch;
private final int workerId;
private int sequence = 0;
private final int shiftTime;
private final int shiftWorker;
private final int sequenceMask;
private final long tolerantClockBackTimestamp;
private long lastTimestamp = -1L;
private final String desc;
/**
* 默认1024个worker,每个worker产生400w个id/秒。
*
* @param workerId
* 唯一值(0-1023)
* @param tolerantClockBackTimestamp
* 容忍时间回拨毫秒数(发生回拨,则等待2倍的容忍时间)
*/
public IDCreator(int workerId, long tolerantClockBackTimestamp) {
this(workerId, tolerantClockBackTimestamp, null);
}
public IDCreator(int workerId, long tolerantClockBackTimestamp, String desc) {
this(workerId, tolerantClockBackTimestamp, default_worker_bits, default_sequence_bits, default_epoch_date,
desc);
}
/**
* workerBits+sequenceBits<=24 (24=64-1-min_time_bits)
*/
protected IDCreator(int workerId, long tolerantClockBackTimestamp, int workerBits, int sequenceBits,
String epochDate, String startupDesc) {
this.tolerantClockBackTimestamp = tolerantClockBackTimestamp;
this.workerId = workerId;
int maxWorkerId = getMaxWorkerId(workerBits);
if (this.workerId < 0 || this.workerId > maxWorkerId) {
throw new IllegalArgumentException(logPrefix() + "WorkerId应为:0-" + maxWorkerId);
}
this.workerBits = workerBits;
this.sequenceBits = sequenceBits;
this.shiftTime = workerBits + sequenceBits;
this.shiftWorker = sequenceBits;
this.sequenceMask = -1 ^ (-1 << sequenceBits);
this.timeBits = 64 - 1 - workerBits - sequenceBits;
if (timeBits < min_time_bits) {
throw new IllegalArgumentException(logPrefix() + "时间戳位数不应小于" + min_time_bits + "(实际" + timeBits
+ "), workerBits与sequenceBits之和应<=" + max_worker_sequence_bits + ".");
}
try {
epoch = sdf_date.parse(epochDate).getTime();
} catch (ParseException e) {
throw new IllegalArgumentException("计时起点日期不正确。");
}
// printInfo
{
long epochNow = System.currentTimeMillis() - epoch;
int year = (int) ((1L << timeBits) / ts_year);
int restOfYear = (int) (((1L << timeBits) - epochNow) / ts_year);
desc = "[" + this + "] 计时起点日期: " + sdf_date.format(epoch) + ";最多服务节点数: " + (1L << workerBits) + "("
+ workerBits + "位)" + "; 每节点产生ID数: " + ((1L << sequenceBits) *
1) + "/秒(" + sequenceBits + "位)"
+ "; 可用" + year + "年,剩余" + restOfYear + "年(" + timeBits + "位)。"
+ (startupDesc == null ? "" : startupDesc);
// desc = "[" + this + "]epoch:" + sdf_date.format(epoch);
}
}
private String logPrefix() {
return "[workerId:" + workerId + "] ";
}
/**
* 获得下一个ID (该方法是线程安全的)
*/
public synchronized long nextId() {
long timestamp = timeGen();
// 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
// 等待
boolean fixedByWait = true;
long offset = lastTimestamp - timestamp;
if (offset <= tolerantClockBackTimestamp && tolerantClockBackTimestamp > 0) {
try {
// wait * 2
wait(tolerantClockBackTimestamp << 1);
} catch (InterruptedException ignored) {
}
timestamp = timeGen();
if (timestamp < lastTimestamp) {
fixedByWait = false;
}
}
if (!fixedByWait) {
throw new IllegalStateException(
logPrefix() + "节点时钟被回拨,不能生成唯一ID,回拨值=" + offset + "ms," +
"当前时间=" + sdf_ts.format(timestamp) + "。");
}
}
// 如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
// 毫秒内序列溢出
if (sequence == 0) {
// 阻塞到下一个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
} else if (sequence != 0) {
// 时间戳改变,毫秒内序列重置
sequence = 0;
}
// 上次生成ID的时间截
lastTimestamp = timestamp;
// 移位并通过或运算拼到一起组成64位的ID
return ((timestamp - epoch) << shiftTime) | (workerId << shiftWorker) | sequence;
}
/**
* 阻塞到下一毫秒
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {
return System.currentTimeMillis();
}
public int getWorkerId() {
return workerId;
}
public String getDescription() {
return desc;
}
@Override
public String toString() {
return getClass().getSimpleName() + "-" + workerId;
}
/**
* @return id创建时间
*/
public Date getCreateTime(long id) {
return new Date((id >> (workerBits + sequenceBits)) + epoch);
}
public static void main(String[] args) throws Exception {
long id = 1324684778368139264L;
IDCreator g = new IDCreator(0, 0);
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(g.getCreateTime(id)));
System.out.println(g.nextId());
System.out.println(g.getDescription());
}
}
测试输出:
2022-01-02 18:10:58.174
639642999193600
[IDCreator-0] 计时起点日期: 2022-01-01;最多服务节点数: 1024(10位); 每节点产生ID数: 4096000/秒(12位); 可用69年,剩余69年(41位)。