雪花算法
雪花算法 - 唯一ID生成算法
原理
雪花算法使用64位long类型的数据存储ID
41位存储毫秒级时间戳,这个时间戳不是存储当前时间的时间戳,而是存储时间截的差值(当前时间戳 - 开始时间戳)得到的值
优点
*1. 能满足高并发分布式系统环境下ID不重复
*2. 基于时间戳,可以保证基本的有序递增
*3. 不依赖第三方的库或中间件
*4. 生成效率极高
实现
雪花算法的实现严重依赖时间,因此如果发现时间回退要抛出异常
IdUtil.getSnowflake(0, 20).nextId()
/**
* @param epochDate 初始化时间起点(null表示默认起始日期),后期修改会导致id重复,如果要修改连workerId dataCenterId,慎用
* @param workerId 工作机器节点id
* @param dataCenterId 数据中心id
* @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳
* @param timeOffset 允许时间回拨的毫秒数
* @since 5.7.3
*/
public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) {
if (null != epochDate) {
this.twepoch = epochDate.getTime();
} else{
// Thu, 04 Nov 2010 01:42:54 GMT
this.twepoch = DEFAULT_TWEPOCH;
}
if (workerId > MAX_WORKER_ID || workerId < 0) {
throw new IllegalArgumentException(StrUtil.format("worker Id can't be greater than {} or less than 0", MAX_WORKER_ID));
}
if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
throw new IllegalArgumentException(StrUtil.format("datacenter Id can't be greater than {} or less than 0", MAX_DATA_CENTER_ID));
}
this.workerId = workerId;
this.dataCenterId = dataCenterId;
this.useSystemClock = isUseSystemClock;
this.timeOffset = timeOffset;
}
/**
获得下一个ID
*/
public synchronized long nextId() {
long timestamp = genTime();
if (timestamp < this.lastTimestamp) {
if(this.lastTimestamp - timestamp < timeOffset){
// 容忍指定的回拨,避免NTP校时造成的异常
timestamp = lastTimestamp;
} else{
// 如果服务器时间有问题(时钟后退) 报错。
throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));
}
}
if (timestamp == this.lastTimestamp) {
final long sequence = (this.sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
this.sequence = sequence;
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << TIMESTAMP_LEFT_SHIFT)
| (dataCenterId << DATA_CENTER_ID_SHIFT)
| (workerId << WORKER_ID_SHIFT)
| sequence;
}
整理生成ID的工具类
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.*
import cn.hutool.core.net.NetUtil;
/**
* ID生成器工具类,此工具类中主要封装:
* 1. 唯一性ID生成器:UUID、ObjectId(MongoDB)、Snowflake
*
* ID相关文章见:http://calvin1978.blogcn.com/articles/uuid.html
*/
public class IdUtil {
// ------------------------------------------------------------------- UUID
/**
* 获取随机UUID
*/
public static String randomUUID() {
return UUID.randomUUID().toString();
}
/**
* 简化的UUID,去掉了横线
*/
public static String simpleUUID() {
return UUID.randomUUID().toString(true);
}
/**
* 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID
*/
public static String fastUUID() {
return UUID.fastUUID().toString();
}
/**
* 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID
*/
public static String fastSimpleUUID() {
return UUID.fastUUID().toString(true);
}
/**
* 创建MongoDB ID生成策略实现<br>
* ObjectId由以下几部分组成:
*
* 1. Time 时间戳。
* 2. Machine 所在主机的唯一标识符,一般是机器主机名的散列值。
* 3. PID 进程ID。确保同一机器中不冲突
* 4. INC 自增计数器。确保同一秒内产生objectId的唯一性。
* 参考:http://blog.csdn.net/qxc1281/article/details/54021882
*
*/
public static String objectId() {
return ObjectId.next();
}
/**
* 创建Twitter的Snowflake 算法生成器。
* 特别注意:此方法调用后会创建独立的{@link Snowflake}对象,每个独立的对象ID不互斥,会导致ID重复,请自行保证单例!
* 分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。
*
* snowflake的结构如下(每部分用-分开):
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
*
* 第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年)
* 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)
* 最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
*
* 参考:http://www.cnblogs.com/relucent/p/4955340.html
*
* @param workerId 终端ID
* @param datacenterId 数据中心ID
* @return {@link Snowflake}
* @deprecated 此方法容易产生起义:多个Snowflake实例产生的ID会产生重复,此对象在单台机器上必须单例!
*/
@Deprecated
public static Snowflake createSnowflake(long workerId, long datacenterId) {
return new Snowflake(workerId, datacenterId);
}
/**
* 获取单例的Twitter的Snowflake 算法生成器对象<br>
* 分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。
*/
public static Snowflake getSnowflake(long workerId, long datacenterId) {
return Singleton.get(Snowflake.class, workerId, datacenterId);
}
/**
* 获取单例的Twitter的Snowflake 算法生成器对象<br>
* 分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。
*/
public static Snowflake getSnowflake(long workerId) {
return Singleton.get(Snowflake.class, workerId);
}
/**
* 获取单例的Twitter的Snowflake 算法生成器对象<br>
* 分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。
*/
public static Snowflake getSnowflake() {
return Singleton.get(Snowflake.class);
}
/**
* 获取数据中心ID<br>
* 数据中心ID依赖于本地网卡MAC地址。
* <p>
* 此算法来自于mybatis-plus#Sequence
* </p>
*
* @param maxDatacenterId 最大的中心ID
* @return 数据中心ID
* @since 5.7.3
*/
public static long getDataCenterId(long maxDatacenterId) {
long id = 1L;
final byte[] mac = NetUtil.getLocalHardwareAddress();
if (null != mac) {
id = ((0x000000FF & (long) mac[mac.length - 2])
| (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
return id;
}
/**
* 获取机器ID,使用进程ID配合数据中心ID生成<br>
* 机器依赖于本进程ID或进程名的Hash值。
*
* <p>
* 此算法来自于mybatis-plus#Sequence
* </p>
*
* @param datacenterId 数据中心ID
* @param maxWorkerId 最大的机器节点ID
* @return ID
* @since 5.7.3
*/
public static long getWorkerId(long datacenterId, long maxWorkerId) {
final StringBuilder mpid = new StringBuilder();
mpid.append(datacenterId);
try {
mpid.append(RuntimeUtil.getPid());
} catch (UtilException igonre) {
//ignore
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
// ------------------------------------------------------------------- NanoId
/**
* 获取随机NanoId
*
* @return 随机NanoId
* @since 5.7.5
*/
public static String nanoId() {
return NanoId.randomNanoId();
}
/**
* 获取随机NanoId
*
* @param size ID中的字符数量
* @return 随机NanoId
* @since 5.7.5
*/
public static String nanoId(int size){
return NanoId.randomNanoId(size);
}
}

浙公网安备 33010602011771号