SnowFlake算法生成的ID值是一个64bit大小的整数,结构图如下
1位
,不用。二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0-
41位
,用来记录时间戳(毫秒)。- 41位可以表示2^41-1个数字,
- 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 2^41-1,减1是因为可表示的数值范围是从0开始算的,而不是1。
- 也就是说41位可以表示2^41-1个毫秒的值,转化成单位年则是69年
-
10位
,用来记录工作机器id。- 可以部署在2^10=1024个节点,包括
5位datacenterId
和5位workerId
5位(bit)
可以表示的最大正整数是,即可以用0、1、2、3、....31这32个数字,来表示不同的datecenterId或workerId
- 可以部署在2^10=1024个节点,包括
-
12位
,序列号,用来记录同毫秒内产生的不同id。12位(bit)
可以表示的最大正整数是,即可以用0、1、2、3、....4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号
由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。
SnowFlake可以保证:
- 所有生成的id按时间趋势递增
- 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)
以下是源码实现
public class IdWorker {
private long workerId;
private long datacenterId;
private long sequence;
public IdWorker(long workerId, long datacenterId, long sequence) {
// sanity check for workerId
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));
}
System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);
this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = sequence;
}
// 起始时间戳,用于用当前时间戳减去这个时间戳,算出偏移量
private long twepoch = 1288834974657L;
// workerId占用的位数
private long workerIdBits = 5L;
// datacenterId占用的位数
private long datacenterIdBits = 5L;
// workerId可以使用的最大数值:31
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
// datacenterId可以使用的最大数值:31
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 序列号占用的位数
private long sequenceBits = 12L;
// workerId左移位数:12
private long workerIdShift = sequenceBits;
// datacenterId左移位数:12+5=17
private long datacenterIdShift = sequenceBits + workerIdBits;
// 时间戳左移位数:12+5+5=22
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
// sequence可以使用的最大数值:4095
private long sequenceMask = -1L ^ (-1L << sequenceBits);
// 上一次生成ID的时间戳
private long lastTimestamp = -1L;
public long getWorkerId() {
return workerId;
}
public long getDatacenterId() {
return datacenterId;
}
public long getTimestamp() {
return System.currentTimeMillis();
}
public synchronized long nextId() {
// 拿到当前时间戳(毫秒)
long timestamp = timeGen();
// 当前时间戳比上次生成ID时间戳还小,时间回拨了,直接抛出异常,否则可能生成重复ID
if (timestamp < lastTimestamp) {
System.err.printf("clock is moving backwards. Rejecting requests until %d.", lastTimestamp);
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
lastTimestamp - timestamp));
}
// 序列号只有在同一毫秒内生成ID,才会去递增,否则为0
// 如果上次生成ID和当前时间戳一致,表示同一毫秒内生成ID,则使用序列号+1,否则序列号为0
if (lastTimestamp == timestamp) {
// sequenceMask是为了防止溢出:始终保证sequence范围为0~4095
// sequenceMask值4095,二进制表示为00001111 11111111
// 当sequence达到4095时,sequence变为0,(4095+1)& 4095转为二进制00010000 00000000 & 00001111 11111111
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 如果同一毫秒内,序列号已到达最大值,则调整当前时间戳直到大于上一次生成ID的时间戳:while循环实现
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
// 当前生成ID赋值给lastTimestamp,记录最近生成ID时间戳
lastTimestamp = timestamp;
// 位移后返回结果值
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
1.位运算n个bit能表示的最大值long maxWorkerId = -1L ^ (-1L << 5L)
的二进制运算过程如下:
// -1左移5,得到结果a:
11111111 11111111 11111111 11111111 //-1的二进制表示(补码)
11111 11111111 11111111 11111111 11100000 //高位溢出的不要,低位补0
11111111 11111111 11111111 11100000 //结果a
// -1异或a:
11111111 11111111 11111111 11111111 //-1的二进制表示(补码)
^ 11111111 11111111 11111111 11100000 //两个操作数的位中,相同则为0,不同则为1
---------------------------------------------------------------------------
00000000 00000000 00000000 00011111 //最终结果31
2.用mask防止溢出
long seqMask = -1L ^ (-1L << 12L); // 计算12位能耐存储的最大正整数,相当于:2^12-1 = 4095
System.out.println("seqMask: "+seqMask);
for(long i=1L; i<Integer.MAX_VALUE; i++){
System.out.println(i & seqMask);
}
// 结果值范围始终是 0-4095 !
3.测试结果
public static void main(String[] args) throws InterruptedException {
IdWorker worker = new IdWorker(1, 1, 0);
ConcurrentSkipListSet<Long> set = new ConcurrentSkipListSet<>();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
Long id = worker.nextId();
if (!set.add(id)) {
System.out.println(id);
}
}
}
});
}
Thread.sleep(1000); // 保证全部ID生成
System.out.println();
System.out.printf("ID COUNT:%d", set.size());
}