package com.seven.base.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
/**
* @author deng
* @Version V1.0.0
* @ClassName SnowId
* @date 2022/3/1 18:06
* @Description 雪花id生成
*/
@Slf4j
@Service
public class SnowUtil {
//region ==============================Fields===========================================
/** 请在nacos上为每一个微服务配置一个不同的workerId */
@Value("${snow.workerId}")
private int workerId;
/** 开始时间截 (2022-01-01 08:08:08) */
private static final long START_EPOCH = 1640995688000L;
/** 雪花算法id的总位数,注意第一位是不用的 */
private static final long DATA_BITS = 64L;
/** 机器id所占的位数,精确到毫秒,可以用69年 */
private static final long TIMESTAMP_BITS = 41L;
/** 机器id所占的位数 */
private static final long WORKER_ID_BITS = 10L;
/** 序列在id中占的位数 */
private static final long SEQUENCE_BITS = 12L;
/** 支持的最大机器id范围(0~1023) */
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
/** 生成序列的掩码,这里为2的12次方-1范围(0~4095) */
private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
/** 时间截向左移22位 */
private static final long TIMESTAMP_LEFT_SHIFT = DATA_BITS - 1 - TIMESTAMP_BITS;
/** 机器ID向左移12位 */
private static final long WORKER_ID_LEFT_SHIFT = SEQUENCE_BITS;
/** 毫秒内序列范围(0~4095) */
private static long sequence = 0L;
/** 上次生成ID的时间截 */
private static long lastTimestamp = -1L;
//endregion
//region ==============================Constructors=====================================
/** 定义静态的util对象 */
private static SnowUtil snowUtil;
/**
* 构造函数
*/
private SnowUtil(){ }
//endregion
//region ==============================Methods==========================================
/**
* workerId初始化
*/
@PostConstruct
private void init(){
snowUtil = this;
}
/**
* 获取id
*/
public static long nextId() throws Exception {
//最好的做法是在nacos上为每一个不同的微服务都配置一个唯一的snow workerId,这样不同的微服务之间就不会生成相同的id了
return nextId(snowUtil.workerId);
}
/**
* 获得下一个ID (该方法是线程安全的)
* @param workerId 工作机器ID(0~31)
* @return SnowflakeId
*/
protected static synchronized long nextId(long workerId) throws Exception {
//workerId判断
if (workerId > MAX_WORKER_ID || workerId < 0) {
String err = String.format("SnowId Class error, workerId: %d,minWorkerId: %d,maxWorkerId: %d,please check", workerId, 0, MAX_WORKER_ID);
log.error(err);
throw new Exception(err);
}
//获取当前时间戳
long timestamp = System.currentTimeMillis();
//如果当前时间戳小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
String err = String.format("SnowId Class error,Clock moved backwards, lastTimestamp: %d,nowTimestamp: %d,diffTimestamp: %d,please check", lastTimestamp, timestamp, lastTimestamp - timestamp);
log.error(err);
throw new Exception(err);
}
//如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & SEQUENCE_MASK;
//毫秒内序列溢出
if (sequence == 0) {
//阻塞到下一个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
}else {
//时间戳改变,毫秒内序列重置
sequence = 0L;
}
//更新上次生成ID的时间截
lastTimestamp = timestamp;
// 1位高位,41位时间戳,10位workerId,12位同一时间毫秒内能生成的序列号码
// 0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000000 00 - 00000000 0000
// 高位0,0与任何数字按位或运算都是本身,所以第一位高位写不写效果都一样
// timestamp、workerId左移,sequence不需要移动,低位补0
// 按位或运算进行二进制拼接
return ((timestamp - START_EPOCH) << TIMESTAMP_LEFT_SHIFT)
| (workerId << WORKER_ID_LEFT_SHIFT)
| sequence;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected static long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
//endregion
}