目录
前提
需要对位运算符有所了解,运算符之位运算符
原理
1,如图所示,整个id的生成,是有三部分组合在一起的
2,第一部分是时间,站位41bit;第二部分是工作机器id,站位10bit;第三部分是序列号,站位12bit。
3,第一部分时间的生成规则是:当前时间戳【System.currentTimeMillis()】,减去初始时间【private long twepoch = 1628850054648L;】,会得到一个时间差,第一位存放的就是这个时间差。
4,第二部分工作机器id规则:有数据中心(5bit)+机器节点(5bit)组合在一起。可以满足多钟场景使用。
5,第三部分序列号规则:一个由0不断自增的整数。当处于高并发的情况下,每毫秒都需要生成大量的id时,只靠时间差来保证唯一性,已经不能满足需求了。序列号,不断自增,由序列表+时间差就能保证唯一性了。
6,核心:把如上三部分,根据前后顺序,拼接在一起,唯一id就出来了。拼接主要是利用位运算符,来实现的。
代码
/**
* 生成id
* 每毫秒可产生4096不同id
* 最多可以使用69.73年
*
* @author frank—fu
*
*/
public class IdGen {
//机器节点
private long workerId;
//数据中心
private long datacenterId;
//序列号(自增)
private long sequence = 0L;
//初始时间(Thu, 04 Nov 2010 01:42:54 GMT)
//主要是用来计算时间差(当前时间-twepoch)
//这个时间最好是系统第一次上线时间,这样可以使用大概69.73年
private long twepoch = 1628850054648L;
//机器节点ID长度
private long workerIdBits = 5L;
//数据中心ID长度
private long datacenterIdBits = 5L;
//最大支持机器节点数0~31,一共32个
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
//最大支持数据中心节点数0~31,一共32个
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
//序列号长度
private long sequenceBits = 12L;
//机器节点需要左移位数(sequenceBits = 12)
private long workerIdShift = sequenceBits;
//数据中心节点需要左移位数(sequenceBits + workerIdBits = 17)
private long datacenterIdShift = sequenceBits + workerIdBits;
//时间毫秒数需要左移位数(sequenceBits + workerIdBits + datacenterIdBits = 22)
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
//sequence的最大值4095,如果为4095,则sequence=0
private long sequenceMask = -1L ^ (-1L << sequenceBits);
//上次获取id的时间,默认为-1
private long lastTimestamp = -1L;
private static class IdGenHolder {
private static final IdGen instance = new IdGen();
}
public static IdGen getInStance() {
return IdGenHolder.instance;
}
public IdGen() {
this(0L, 0L);
}
public IdGen(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();
//如果服务器时间有问题(时钟后退) 报错。
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果上次生成时间和当前时间相同,在同一毫秒内
if (lastTimestamp == timestamp) {
//sequence自增,因为sequence只有12bit,所以和sequenceMask相与一下,去掉高位
sequence = (sequence + 1) & sequenceMask;
//判断是否溢出,也就是每毫秒内超过4095,当为4096时,与sequenceMask相与,sequence就等于0
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp); //自旋等待到下一毫秒
}
} else {
//如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加
sequence = 0L;
}
//保存本次生成id的时间
lastTimestamp = timestamp;
//核心计算
//1,(timestamp - twepoch) << timestampLeftShift (当前时间减去初始时间)的值左移22位
//2,datacenterId << datacenterIdShift 数据中心左移17位
//3,workerId << workerIdShift 机器节点左移12位
//利用|的特性,把数据拼接起来
return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {
return System.currentTimeMillis();
}
}
深度解析
1,为什么1毫秒最多可以产生4096个id?
2,为什么最好只能使用69年?
3,为什么最多支持1024个机器节点?
4096
序列号是12bit组成:1111 1111 1111 转成二进制 4095
69
整个函数return的是long【public synchronized long nextId()】;long 占64bit,64-1(占位符)-10(机器)-12(序列号)= 41 bit;也就是说时间差最多只能占41bit
转成十进制,也就是2199023255551,时间差最大的值就是2199023255551毫秒。69年=2,175,984,000,000毫秒
1024
机器占10bit(数据中心(5bit)+机器节点(5bit)),1111111111 转成十进制是1023,加上0,就是1024
总结
1,1毫秒最多产生4096个不同的id
2,最多可使用69年
3,最多可以支持1024个机器