1 雪花算法原理
SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图:
- 1bit,不用,二进制中最高位是符号位,0表示正数,固定是0
- 41bit,毫秒时间戳,2^41-1毫秒时间戳表示可使用69年
- 10bit,工作机器id,前5位表示机房id,后5位表示机器id,最大支持2^5 * 2^5=1024个机器
- 12bit,序列号,表示同一机器同一时间戳内可以产生的4096个ID序号
2 算法实现(java)
package com.demo.utils;
/**
* @author huwenlong
* @version 1.0
* @date 2020/9/17 8:40 下午
*/
public class SnowFlake {
/**
* 时间偏移量,从2020-09-17 20:41:42开始算起
*/
private final long twepoch = 1600346502216L;
/**
* 时间戳偏移量
*/
private final int timestampOffset = 22;
/**
* 机房id
*/
private final long datacenterId;
/**
* 机房id偏移量
*/
private final int datacenterIdOffset = 17;
/**
* 机器id
*/
private final long workerId;
/**
* 机器id偏移量
*/
private final int workerIdOffset = 12;
/**
* 当前序号
*/
private long currentSequence = 0L;
/**
* 最大序号
*/
private final int maxSequence = 4095;
/**
* 最后一次生成id的时间
*/
private long lastTimestamp = 0L;
public SnowFlake(long datacenterId, long workerId) {
this.datacenterId = datacenterId;
this.workerId = workerId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
// 时钟回调抛异常
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
// 如果同一毫秒内生成id则currentSequence自增
currentSequence = (currentSequence + 1) & maxSequence;
if (currentSequence == 0) {
//当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
timestamp = tilNextMillis(lastTimestamp);
}
} else {
// 进入下一毫秒,从0开始
currentSequence = 0;
}
// 更新lastTimestamp
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampOffset) | (datacenterId << datacenterIdOffset) | (workerId << workerIdOffset) | currentSequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
3 算法测试
3.1 效果测试
public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(1, 1);
for (int i = 0; i < 10; i++) {
System.out.println(snowFlake.nextId());
}
}
输出
13181447704576
13181451898880
13181451898881
13181451898882
13181451898883
13181451898884
13181451898885
13181451898886
13181451898887
13181451898888
3.2 正确性测试
public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(1, 1);
Set<Long> set = new HashSet<>(10000);
for (int i = 0; i < 3000000; i++) {
set.add(snowFlake.nextId());
}
Assert.isTrue(set.size() == 3000000, "error");
}
3.3 效率测试
public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(1, 1);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 3000000; i++) {
snowFlake.nextId();
}
System.out.println("cost:" + (System.currentTimeMillis() - startTime));
}
输出:cost:734,300w个id仅需要734ms,效率还是不错的