1 概述
雪花算法,SnowFlake,是Twitter公司开源的由64位整数组成的分布式ID算法,目的是在分布式系统中产生全局唯一且趋势递增的ID,以解决分布式系统中ID全局唯一、递增、确保在任何时候都能生成正确ID、高并发生成ID的需求。
2 组成部分
(1) 第一位,占用1bit,其值始终是0,表示是正整数;
(2) 2-42位,占用41bit,表示时间戳,时间是毫秒,总共可以容纳69年的时间;
(3) 43-52位,占用10bit,记录工作机器的ID,10bit里面可以按需要拆分数位作为机房ID,其他作为服务器ID,例如5bit为机房ID,可以部署到2^5个机房,另外5bit为服务器ID,每个机房可以部署2^5台服务器;
(4) 53-64位,占用12bit,用来记录同一个毫秒内产生的不同ID,12bit可以代表的最大正整数是2^12-1=4096个,也就是说同一毫秒内同一台机器所生成的最大ID数量为4096个;
3 雪花算法的实现
package com.allawn.loadbalance.algorithm;
import com.allawn.AllawnException;
import com.allawn.DateUtils;
import lombok.extern.log4j.Log4j2;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
/**
* Snow Flake Algorithm
*
* @author Blake
* @date 2022-02-08 10:58:11
**/
@Log4j2
public final class SnowFlakeWorker {
/**
* bits for sequence
**/
private static final long SEQUENCE_BITS = 12L;
/**
* mask for sequence
**/
private final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
/**
* data center id
**/
private final long dataCenterId;
/**
* bits for data center id
**/
private final long dataCenterIdBits;
/**
* offset for data center id
**/
private final long dataCenterIdShift;
/**
* offset for timestamp
**/
private final long timestampShift;
/**
* worker id
**/
private final long workerId;
/**
* bits for worker id
**/
private final long workerIdBits;
/**
* last timestamp
**/
private long lastTimestamp = -1L;
/**
* sequence in milliseconds
**/
private long sequence;
/**
* start time stamp
**/
private long startTimestamp;
private SnowFlakeWorker(Builder builder) {
this.workerIdBits = builder.workerIdBits;
this.dataCenterIdBits = builder.dataCenterIdBits;
try {
startTimestamp = DateUtils.parseLong("2000-01-01 00:00:00");
} catch (AllawnException e) {
log.error("generate start timestamp error", e);
}
this.workerId = builder.workerId;
this.dataCenterId = builder.dataCenterId;
this.dataCenterIdShift = SEQUENCE_BITS + workerIdBits;
this.timestampShift = SEQUENCE_BITS + workerIdBits + dataCenterIdBits;
}
/**
* generate next id
*
* @throws AllawnException when lastTimestamp less than timestamp
* @author Blake
* @date 2022-02-08 12:38:18
**/
public synchronized long nextId() throws AllawnException {
AtomicLong timestamp = new AtomicLong(System.currentTimeMillis());
AtomicBoolean throwException = new AtomicBoolean(false);
Optional.of(timestamp.get()).filter(ts -> ts == lastTimestamp).ifPresentOrElse(ts -> {
// if timestamp equals to lastTimestamp
sequence = (sequence + 1) & SEQUENCE_MASK;
// if sequence equals 0
Optional.of(sequence).filter(s -> s == 0).ifPresent(s -> timestamp.set(tilNextMillis(lastTimestamp)));
}, () -> Optional.of(timestamp.get()).filter(ts -> ts > lastTimestamp).ifPresentOrElse(
// if timestamp greater than lastTimestamp
ts -> sequence = 0L,
// if timestamp less than lastTimestamp
() -> throwException.set(true)));
Optional.of(throwException).filter(te -> !te.get()).orElseThrow(() -> new AllawnException(
"Clock moved backwards, refusing to generate id for " + (lastTimestamp - timestamp.get()) +
" milliseconds"));
lastTimestamp = timestamp.get();
return ((timestamp.get() - startTimestamp) << timestampShift) | (dataCenterId << dataCenterIdShift) |
(workerId << SEQUENCE_BITS) | sequence;
}
/**
* generate next timestamp after lastTimestamp
*
* @param lastTimestamp lastTimestamp
* @return timestamp
* @author Blake
* @date 2022-02-08 12:37:31
**/
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
@Override
public String toString() {
return "startTimestamp is " + startTimestamp + ", workerIdBits is " + workerIdBits + ", workerId is " +
workerId + ", dataCenterIdBits is " + dataCenterIdBits + ", dataCenterId is " + dataCenterId;
}
/**
* SnowFlakeWorker Builder
*
* @author Blake
* @date 2022-02-08 12:38:01
**/
public static final class Builder {
/**
* 数据中心ID
**/
private long dataCenterId;
/**
* 数据标识ID所占的位数
**/
private long dataCenterIdBits = 5L;
/**
* 工作机器ID
**/
private long workerId;
/**
* 机器ID所占的位数
**/
private long workerIdBits = 5L;
/**
* build a SnowFlakeWorker Object
*
* @return SnowFlakeWorker Object
* @throws AllawnException when dataCenterIdBits plus workerIdBits is not equals 10
* @author Blake
* @date 2022-02-08 12:00:36
**/
public SnowFlakeWorker build() throws AllawnException {
Optional.of(this).filter(builder -> builder.dataCenterIdBits + builder.workerIdBits == 10)
.orElseThrow(() -> new AllawnException("dataCenterIdBits plus workerIdBits must equals 10"));
return new SnowFlakeWorker(this);
}
/**
* set dataCenterId
*
* @param dataCenterId dataCenterId
* @return Builder Object
* @throws AllawnException when data center id greater than maxDataCenterId or less than 0
* @author Blake
* @date 2022-02-08 12:01:39
**/
public Builder dataCenterId(long dataCenterId) throws AllawnException {
this.dataCenterId = dataCenterId;
long maxDataCenterId = ~(-1L << dataCenterIdBits);
Optional.of(dataCenterId).filter(id -> id <= maxDataCenterId && id > 0).orElseThrow(
() -> new AllawnException(
"data center id can't be greater than " + maxDataCenterId + " or less than 0"));
return this;
}
/**
* set dataCenterIdBits
*
* @param dataCenterIdBits dataCenterIdBits
* @return Builder Object
* @author Blake
* @date 2022-02-08 12:02:21
**/
public Builder dataCenterIdBits(long dataCenterIdBits) {
this.dataCenterIdBits = dataCenterIdBits;
return this;
}
/**
* set workerId
*
* @param workerId workerId
* @return Builder Object
* @throws AllawnException when worker id greater than maxWorkerId or less than 0
* @author Blake
* @date 2022-02-08 12:02:46
**/
public Builder workerId(long workerId) throws AllawnException {
this.workerId = workerId;
long maxWorkerId = ~(-1L << workerIdBits);
Optional.of(workerId).filter(id -> id <= maxWorkerId && id > 0).orElseThrow(
() -> new AllawnException("worker id can't be greater than " + maxWorkerId + " or less than 0"));
return this;
}
/**
* set workerIdBits
*
* @param workerIdBits workerIdBits
* @return Builder Object
* @author Blake
* @date 2022-02-08 12:03:16
**/
public Builder workerIdBits(long workerIdBits) {
this.workerIdBits = workerIdBits;
return this;
}
}
}
(1) 已知,第53-64位为同一个毫秒内同一台机器(同一个机房dataCenter,同一个服务器worker)的序列号,那么按同一毫秒timestamp、同一机房dataCenter、同一服务器worker不断递增sequence,再与111111111111(12个1,即序号占12位)进行位与运算&即可得到第53-64位的bit值;
(2) 已知,第43-52位为工作机器的ID,那么:
1) 先指定机房ID占用的bit数dataCenterIdBits,再指定服务器ID占用的bit数workerIdBits;
2) 计算服务器ID的bit值,把给定的服务器ID,即workerId向左位移12位(因其右边是12位sequence)即可;
3) 计算机房ID的bit值,把给定的机房ID,即dataCenterId向左位移workerIdBits+12即可,因其右边是workerIdBits位的workerId和12位的sequenct;
(3) 已知,第2-42位是时间戳的bit值,把timestamp向左位移dataCenterIdBits+workerIdBits+12即可,因其右边是dataCenterIdBits位的dataCenterId、workerIdBits位的workerId和12位的sequence;
(4) 已知,第1位是0表示整数,无需计算,如需要表示成bit值,则应是64个0;
(5) 把上述计算的预留数bit值、时间戳bit值、机房bit值、服务器bit值、序列号bit值进行位或运算,得到需要的ID值;
注:如果把雪花算法算出来的ID转化成bit值,可能会出现总长度只有62位的情况,这是因为第1位0表示这是一个正整数,第2位也是0,表示时间戳算出来的结果也是正整数,转化成bit值后前面的两个0被省略了。