雪花算法(Snowflake)
Snowflake 中文的意思为雪花,Snowflake算法 常被称为 雪花算法,是 Twitter 开源的分布式 ID 生成算法,是一种分布式主键ID生成的解决方案。
使用场景
分布式唯一id生成,我们通俗讲的雪花算法是 64 字节的版本,当然如果有需求也可以自行实现其它版本。
优缺点
优点:
优点 | 描述 |
---|---|
高吞吐 | 每秒能生成百万个不重复的id |
高性能 | 不依赖其它纯java代码就能实现 |
缺点:
缺点 | 描述 |
---|---|
时钟回拨会导致id重复 | 由人为或其它原因导致的时钟回拨会导致id重复 |
雪花算法深入了解
标准算法是64bit位表示,拆分如下,如果有需要可以重新划分,如只需要时间到秒,则时间可以少10bit位,可以把此10bit位分配到其它需要的位置。注意: 虽然可以重新划分,不过为满足分布式需求,最好结构不变,即要有符号位、时间、机器id、数据中心id、自增id这几部分
代码及解释
1. 机器id和数据中心id在分布式场景下需要保证唯一,否则生成的id可能会重复
2. 计算说明
-1L << x # 将-1 左移x 位;
-1L ^ (-1L << x) # 将-1 左移x 位之后最大值, 等价于 ~ (-1L << x)
3. 雪花算法生成的id长度
雪花算法生成的长度一般为18或19位,因为64字节正好对应java的long型,保留第一位作为符号位,剩63bit位,生成的最大值就是19位。
4. 代码
// 包名替换
package xxxx;
import java.io.Serializable;
import java.util.Date;
/**
* 雪花算法
*/
public class Snowflake implements Serializable {
private static final long serialVersionUID = 1L;
/** 起始时间戳 */
private final long twepoch;
/** 机器id字节数 */
private final long workerIdBits = 5L;
/** 数据中心字节数 */
private final long dataCenterIdBits = 5L;
最大支持机器节点数0~31,一共32个
// 最大支持数据中心节点数0~31,一共32个
/** 最大支持的workerId 0~31 */
@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/** 最大支持的数据中心id 0~31 */
@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);
/** 序列号12bit位 */
private final long sequenceBits = 12L;
/** 序号最大值 */
@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/** 机器节点左移12bit位 */
private final long workerIdShift = sequenceBits;
/** 数据中心节点左移17bit位 */
private final long dataCenterIdShift = sequenceBits + workerIdBits;
/** 时间毫秒数左移22位 */
private final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
/** 初始化的workerId */
private final long workerId;
/** 初始化的数据中心id */
private final long dataCenterId;
/** 起始编号 */
private long sequence = 0L;
/** 上次的时间 */
private long lastTimestamp = -1L;
/**
* 构造
*
* @param workerId workerId
* @param dataCenterId 数据中心id
*/
public Snowflake(long workerId, long dataCenterId) {
this(null, workerId, dataCenterId);
}
/**
* @param epochDate 初始化时间起点(null表示默认起始日期),后期修改可能会导致id重复
* @param workerId 工作机器节点id
* @param dataCenterId 数据中心id
*/
public Snowflake(Date epochDate, long workerId, long dataCenterId) {
if (null != epochDate) {
this.twepoch = epochDate.getTime();
} else {
// 2020-01-01 00:00:00
this.twepoch = 1577808000000L;
}
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("workerId %s 超过最大值 %s", workerId, maxWorkerId));
}
if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
throw new IllegalArgumentException(String.format("dataCenterId %s 超过最大值 %s", dataCenterId, maxDataCenterId));
}
this.workerId = workerId;
this.dataCenterId = dataCenterId;
}
/**
* 生成的id,获取机器id
*
* @param id 生成的id
* @return workerId
*/
public long getWorkerId(long id) {
return id >> workerIdShift & ~(-1L << workerIdBits);
}
/**
* 生成的id,获取数据中心id
*
* @param id 生成的id
* @return 所属数据中心
*/
public long getDataCenterId(long id) {
return id >> dataCenterIdShift & ~(-1L << dataCenterIdBits);
}
/**
* 生成的id,获取生成时间
*
* @param id 生成的id
* @return 生成的时间
*/
public long getGenerateDateTime(long id) {
return (id >> timestampLeftShift & ~(-1L << 41L)) + twepoch;
}
/**
* 生成的id,获取生成的序号
*
* @param id 生成的id
* @return 序号id
*/
public long getSequenceId(long id) {
return id & ~(-1L << sequenceBits);
}
/**
* 下一个ID
*
* @return ID
*/
public synchronized long nextId() {
long timestamp = genTime();
if (timestamp < lastTimestamp) {
// 如果服务器时间有问题(时钟后退) 报错。
throw new IllegalStateException(String.format("时钟回退 %s", lastTimestamp - timestamp));
//容许时钟回退
// if (lastTimestamp - timestamp < 2000) {
// // 容忍2秒内的回拨,避免NTP校时造成的异常
// timestamp = lastTimestamp;
// } else {
// // 如果服务器时间有问题(时钟后退) 报错。
// throw new IllegalStateException(String.format("时钟回退 %s", lastTimestamp - timestamp));
// }
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence;
}
/**
* 下一个ID(字符串形式)
*
* @return ID 字符串形式
*/
public String nextIdStr() {
return Long.toString(nextId());
}
// ------------------------------------------------------------------------------------------------------------------------------------ Private method start
/**
* 循环等待下一个时间
*
* @param lastTimestamp 上次记录的时间
* @return 下一个时间
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = genTime();
// 循环直到操作系统时间戳变化
while (timestamp == lastTimestamp) {
timestamp = genTime();
}
if (timestamp < lastTimestamp) {
// 如果发现新的时间戳比上次记录的时间戳数值小,说明操作系统时间发生了倒退,报错
throw new IllegalStateException(
String.format("时钟倒退 %sms", lastTimestamp - timestamp));
}
return timestamp;
}
/**
* 生成当前时间戳
*
* @return 时间戳
*/
private long genTime() {
return System.currentTimeMillis();
}
}
// 包名替换
package xxx;
import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* 雪花算法
* 1. 代码中的8080需要换成实际服务的端口
* 2. workerId和centerDataId 也可以改为读取配置文件
*/
public class SnowIdUtil {
private SnowIdUtil() {
//private
}
private static final Object LOCK = new Object();
private static volatile Snowflake snowflake = null;
private static final SecureRandom RANDOM = new SecureRandom();
public static final int THIRTY_ONE = 31;
public static final int THIRTY_TWO = 32;
/**
* 下个id 一毫秒最大 4096个,如果超过慎用
*
* @return id
*/
public static long nextId() {
if (snowflake == null) {
synchronized (LOCK) {
if (snowflake == null) {
snowflake = new Snowflake(getWorkId(), getDataId());
}
}
}
return snowflake.nextId();
}
@SuppressWarnings("java:S3252")
public static int getWorkId() {
try {
//8080为服务器端口,实际需要修改为动态获取
String s = Inet4Address.getLocalHost().getHostAddress().concat(":").concat(String.valueOf(8080));
return getHostId(s, THIRTY_ONE);
} catch (UnknownHostException var1) {
return (RANDOM.nextInt(THIRTY_TWO));
}
}
@SuppressWarnings("java:S3252")
public static int getDataId() {
try {
return getHostId(Inet4Address.getLocalHost().getHostName(), THIRTY_ONE);
} catch (UnknownHostException var1) {
return (RANDOM.nextInt(THIRTY_TWO));
}
}
/**
* 获取时间
*
* @param id snowflake算法生成的id
* @return 时间
*/
@SuppressWarnings("java:S3252")
public static LocalDateTime getDateTime(long id) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(snowflake.getGenerateDateTime(id)), ZoneId.systemDefault());
}
/**
* 根据Snowflake的ID,获取序号id
*
* @param id snowflake算法生成的id
* @return 序号id
*/
@SuppressWarnings("java:S3252")
public static long getSequenceId(long id) {
return snowflake.getSequenceId(id);
}
private static int getHostId(String s, int max) {
byte[] bytes = s.getBytes();
int sums = 0;
byte[] var4 = bytes;
int var5 = bytes.length;
for (int var6 = 0; var6 < var5; ++var6) {
int b = var4[var6];
sums += b;
}
return sums % (max + 1);
}
public static void main(String[] args) {
long l = nextId();
LocalDateTime dateTime = getDateTime(l);
long sequenceId = getSequenceId(l);
System.out.println(dateTime+" "+ sequenceId);
}
}