SnowFlakeUtils 雪花算法工具类。
@Slf4j
public class SnowFlakeUtils {
private static final RedisOperation REDIS_OPERATION = ApplicationContextHelper.getBean(RedisOperation.class);
private static final String LOCAL_IP = getLocalIp();
private static volatile SnowFlakeUtils instance;
/**
* 该任务开始时间,必须手动设置(差值的唯一性)
* 建议在生产部署时选择某一日的0时0分0秒0毫秒的时间戳,方便计算
*/
private static final long START_TIME = 1588733692671L;
/**
* 各个位的位数,Timestamp为41L(无需定义)
*/
private static final long DATA_CENTER_ID_BITS = 5L;
private static final long WORKER_ID_BITS = 1L;
private static final long SEQUENCE_BITS = 16L;
/**
* 各位的最大值
*/
private static final long DATA_CENTER_ID_MAX = ~(-1 << DATA_CENTER_ID_BITS);
private static final long WORKER_ID_MAX = ~(-1 << WORKER_ID_BITS);
private static final long SEQUENCE_MAX = ~(-1 << SEQUENCE_BITS);
/**
* 各位应该向左移动位数
*/
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
/**
* 数据中心ID
*/
private final long dataCenterId;
private static final String DATA_CENTER_ID = "DATACENTERID";
/**
* 工作线程ID
*/
private final long workerId;
private static final String WORKER_ID = "WORKERID";
/**
* 序列号
*/
private long sequence = 0L;
/**
* 上次时间(保证不回退)
*/
private long lastTimestamp = -1L;
/***
* 是否在高并发下
*/
private boolean isClock = false;
public static SnowFlakeUtils getInstance() {
if (instance == null) {
synchronized (SnowFlakeUtils.class) {
if (instance == null) {
int dataCenterId = 0;
int workerId = 0;
while (true) {
// tryCatch保证即使redis等出现问题也可以保证当前线程阻塞,重启redis即可处理继续处理
try {
String replace = RedisKeyConstant.SNOW_FLAKE_KEY.
replace(DATA_CENTER_ID, String.valueOf(dataCenterId)).
replace(WORKER_ID, String.valueOf(workerId));
if (REDIS_OPERATION.setnx(replace, LOCAL_IP, 1, TimeUnit.MINUTES)) {
instance = new SnowFlakeUtils(dataCenterId, workerId);
break;
}
// 进行重新set直至成功,目前只运用dataCenterId
if (dataCenterId++ == DATA_CENTER_ID_MAX) {
log.error("SnowFlake is getting CacheLock, please checkDATACENTERID_MAX={}", DATA_CENTER_ID_MAX);
dataCenterId = 0;
}
} catch (Exception e) {
log.error("SnowFlakeUtils get CacheLock Error, errorMsg:", e);
try {
Thread.sleep(MagicNum.THOUSAND);
} catch (InterruptedException ex) {
log.error(ex.getMessage(), ex);
}
}
}
}
}
}
return instance;
}
public SnowFlakeUtils(long dataCenterId, long workerId) {
if (dataCenterId > DATA_CENTER_ID_MAX || dataCenterId < 0) {
throw new IllegalArgumentException(String.format("data center id can't be greater than %d or less than 0", DATA_CENTER_ID_MAX));
}
if (workerId > WORKER_ID_MAX || workerId < 0) {
throw new IllegalArgumentException(String.format("worker id can't be greater than %d or less than 0", WORKER_ID_MAX));
}
this.dataCenterId = dataCenterId;
this.workerId = workerId;
String key = RedisKeyConstant.SNOW_FLAKE_KEY.
replace(DATA_CENTER_ID, String.valueOf(dataCenterId)).
replace(WORKER_ID, String.valueOf(workerId));
log.info("SnowFlakeUtils Cache Key={}", key);
// 起线程保证workerId和dataCenter组合不重复
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
log.debug("SnowFlakeUtils is keep geting CacheLock-{}", key);
String localIp = REDIS_OPERATION.get(key);
if (LOCAL_IP.equals(localIp)) {
REDIS_OPERATION.setex(key, LOCAL_IP, 1, TimeUnit.MINUTES);
} else if (!REDIS_OPERATION.setnx(key, LOCAL_IP, 1, TimeUnit.MINUTES)) {
throw new ProcessException(CommonConstants.ENUM_PROCESSING_EXCEPTION,"SnowFlakeUtils losed CacheLock-" + key + "." +
"CacheLockKeeperThread broken!" +
"Reday to retrieve CacheLock and Single Instance!");
}
Thread.sleep(MagicNum.FIFTY * MagicNum.THOUSAND);
} catch (Exception e) {
// 发生异常 将单例清除 并退出循环结束子线程
synchronized (SnowFlakeUtils.class) {
instance = null;
}
log.error(e.getMessage(),e);
break;
}
}
}
});
thread.setName("SnowFlake-CacheLockKeeper-" + dataCenterId + "-" + workerId);
thread.start();
}
public void setClock(boolean clock) {
this.isClock = clock;
}
public synchronized long nextId() {
long timestamp = this.getTime();
if (timestamp < lastTimestamp) {
long offset = lastTimestamp - timestamp;
if (offset <= MagicNum.FIVE) {
try {
this.wait(offset << 1);
timestamp = this.getTime();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards, Refusing to generate id for %d milliseconds", offset));
}
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
} else {
throw new RuntimeException(String.format("Clock moved backwards, Refusing to generate id for %d milliseconds", offset));
}
}
if (lastTimestamp == timestamp) {
sequence = sequence + 1;
if (sequence > SEQUENCE_MAX) {
timestamp = tilNextMillis(timestamp);
sequence = 0;
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
return ((timestamp - START_TIME) << TIMESTAMP_SHIFT) |
(dataCenterId << DATA_CENTER_ID_SHIFT) |
(workerId << WORKER_ID_SHIFT) |
sequence;
}
/**
* 该毫秒达到上限,等待到下1毫秒
*/
private long tilNextMillis(long timestamp) {
while (getTime() <= timestamp) {
log.debug("单毫秒主键生成达到上限");
}
return this.getTime();
}
private long getTime() {
if (isClock) {
return SystemClock.currentTimeMillis();
} else {
return System.currentTimeMillis();
}
}
private static String getLocalIp() {
String ip = "";
try {
InetAddress addr = InetAddress.getLocalHost();
ip += addr.getHostAddress();
} catch (Exception e) {
ip += "127.0.0.1";
}
ip += "_" + System.currentTimeMillis() + "_" + Math.random();
log.info("SnowFlakeUtils Cache Value={}", ip);
return ip;
}
}
SystemClock工具类。
/**
* 由于高并发,在同一毫秒中会多次获取currentTimeMillis,而每次使用System.currentTimeMillis都会占用CPU(native方法).
* 于是自定义类(single)来获取currentTimeMillis,实现方法是在此类中定义时间并设置一个周期任务(定时线程)1毫秒更新类中的时间
*/
public final class SystemClock {
private static final SystemClock INSTANCE = new SystemClock(1);
public static SystemClock getInstance() {
return INSTANCE;
}
/**
* 更新时间的时间间隔,默认为1毫秒
*/
private final long period;
/**
* 当前时间
*/
private final AtomicLong now;
private SystemClock(long period) {
this.period = period;
this.now = new AtomicLong(System.currentTimeMillis());
scheduleClockUpdate();
}
/**
* 定时任务(设置为守护线程,1毫秒后开始更新)
* scheduleAtFixedRate: 每次开始间隔为1毫秒
* scheduleWithFixedDelay: 每次结束与开始为1毫秒
*/
private void scheduleClockUpdate() {
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "System Clock");
thread.setDaemon(true);
return thread;
}
});
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
now.set(System.currentTimeMillis());
}
}, period, period, TimeUnit.MILLISECONDS);
}
public static long currentTimeMillis() {
return getInstance().now.get();
}
}
ApplicationContextHelper Spring上下文工具类。
@Slf4j
@Component
public class ApplicationContextHelper implements ApplicationContextAware {
/**
* Spring上下文
*/
private static ApplicationContext applicationContext;
/**
* @return ApplicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 获取ApplicationContextAware
*
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
ApplicationContextHelper.applicationContext = applicationContext;
}
/**
* 根据Class获取对应实例
*
*/
public static <T> T getBean(Class<T> clz) {
return applicationContext.getBean(clz);
}
/**
* 根据beanName获取对应实例
*/
public static <T> T getBean(String name, Class<T> requiredType) {
return applicationContext.getBean(name, requiredType);
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
}
RedisOperation获取 RedisOperation,Redis操作工具类。
在Controller里编写接口,测试结果。
@RestController
@RequestMapping("/part/util")
public class UtilController {
@ApiOperation("获取雪花数字")
@GetMapping("/getSnowFlakeNo")
public Result getSnowFlakeNo() {
return Result.ok().data(String.valueOf(SnowFlakeUtils.getInstance().nextId()));
}
}