ID生成器之雪花算法
2020-05-15 本文已影响0人
扮鬼之梦
简介
雪花算法是分布式ID生成算法,原理可百度,比较详细。
实现一,分布式使用
public class IdWorker {
//下面两个每个5位,加起来就是10位的工作机器id
private long workerId; //工作id
private long datacenterId; //数据id
//12位的序列号
private long sequence;
public IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("workerId取值范围为[ 0 , %d ]", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenterId取值范围为[ 0 , %d ]", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = 0L;
}
//初始时间戳
private long twepoch = 1288834974657L;
private long workerIdBits = 5L;
private long maxWorkerId = -1L ^ (-1L << workerIdBits);//31
private long datacenterIdBits = 5L;
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);//31
private long sequenceBits = 12L;
private long sequenceMask = -1L ^ (-1L << sequenceBits);//4095
//工作id需要左移的位数,12位
private long workerIdShift = sequenceBits;
//数据id需要左移位数 12+5=17位
private long datacenterIdShift = sequenceBits + workerIdBits;
//时间戳需要左移位数 12+5+5=22位
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
//上次时间戳,初始值为负数
private long lastTimestamp = -1L;
public long getWorkerId() {
return workerId;
}
public long getDatacenterId() {
return datacenterId;
}
public long getTimestamp() {
return System.currentTimeMillis();
}
//下一个ID生成算法
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
//获取当前时间戳如果小于上次时间戳,则表示时间戳获取出现异常
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));
}
//获取当前时间戳如果等于上次时间戳(同一毫秒内),则在序列号加一;否则序列号赋值为0,从0开始。
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
//将上次时间戳值刷新
lastTimestamp = timestamp;
/**
* 返回结果:
* (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳,再左移相应位数
* (datacenterId << datacenterIdShift) 表示将数据id左移相应位数
* (workerId << workerIdShift) 表示将工作id左移相应位数
* | 是按位或运算符,例如:x | y,只有当x,y都为0的时候结果才为0,其它情况结果都为1。
* 因为个部分只有相应位上的值有意义,其它位上都是0,所以将各部分的值进行 | 运算就能得到最终拼接好的id
*/
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
//获取时间戳,并与上次时间戳比较
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
实现二,单机使用
import java.sql.Timestamp;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class SystemClock {
private final long period;
private final AtomicLong now;
private SystemClock(long period) {
this.period = period;
this.now = new AtomicLong(System.currentTimeMillis());
this.scheduleClockUpdating();
}
private static SystemClock instance() {
return SystemClock.InstanceHolder.INSTANCE;
}
public static long now() {
return instance().currentTimeMillis();
}
public static String nowDate() {
return (new Timestamp(instance().currentTimeMillis())).toString();
}
private void scheduleClockUpdating() {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor((runnable) -> {
Thread thread = new Thread(runnable, "System Clock");
thread.setDaemon(true);
return thread;
});
scheduler.scheduleAtFixedRate(() -> {
this.now.set(System.currentTimeMillis());
}, this.period, this.period, TimeUnit.MILLISECONDS);
}
private long currentTimeMillis() {
return this.now.get();
}
private static class InstanceHolder {
public static final SystemClock INSTANCE = new SystemClock(1L);
private InstanceHolder() {
}
}
}
public class Sequence {
private static final Sequence instance = new Sequence(0L, 0L);
private final long startTime = 1519740777809L;
private final long workerIdBits = 5L;
private final long dataCenterIdBits = 5L;
private final long sequenceBits = 12L;
private final long maxWorkerId = 31L;
private final long maxDataCenterId = 31L;
private final long workerIdShift = 12L;
private final long dataCenterIdShift = 17L;
private final long timestampLeftShift = 22L;
private final long sequenceMask = 4095L;
private long workerId;
private long dataCenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
private boolean isClock = false;
public Sequence(long workerId, long dataCenterId) {
if (workerId <= 31L && workerId >= 0L) {
if (dataCenterId <= 31L && dataCenterId >= 0L) {
this.workerId = workerId;
this.dataCenterId = dataCenterId;
} else {
throw new IllegalArgumentException(String.format("dataCenter Id can't be greater than %d or less than 0", 31L));
}
} else {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", 31L));
}
}
public static synchronized Long next() {
return instance.nextId();
}
public void setClock(boolean clock) {
this.isClock = clock;
}
public synchronized Long nextId() {
long timestamp = this.timeGen();
if (timestamp < this.lastTimestamp) {
long offset = this.lastTimestamp - timestamp;
if (offset > 5L) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
try {
this.wait(offset << 1);
timestamp = this.timeGen();
if (timestamp < this.lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
} catch (Exception var6) {
throw new RuntimeException(var6);
}
}
if (this.lastTimestamp == timestamp) {
this.sequence = this.sequence + 1L & 4095L;
if (this.sequence == 0L) {
timestamp = this.tilNextMillis(this.lastTimestamp);
}
} else {
this.sequence = 0L;
}
this.lastTimestamp = timestamp;
return timestamp - 1519740777809L << 22 | this.dataCenterId << 17 | this.workerId << 12 | this.sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp;
for(timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) {
}
return timestamp;
}
private long timeGen() {
return this.isClock ? SystemClock.now() : System.currentTimeMillis();
}
}
两种方法生成id验证
@Test
public void idGenerator(){
//此为方法一,web项目中的可以注入此类,则为单例的,分布式部署,不同服务器设置不同的workerId和datacenterId
IdWorker worker = new IdWorker(0,0);
int total = 2000000;
HashSet<Long> ids = new HashSet<>(total);
long start = System.currentTimeMillis();
for (int i = 0; i < total; i++) {
long l = worker.nextId();
//此为方法二,直接Sequence.next()即可得到id
// long l = Sequence.next();
ids.add(l);
}
long end = System.currentTimeMillis();
//若通过断言,则证明产生了total个不同的id
Assert.assertEquals(ids.size(),total);
System.out.println(String.format("生成%s个id,耗时%sms",total,end-start));
}