简介
由于工作中需要提供通用的唯一ID生成方案,调研了目前市面上通用的解决方案,最终经过汇总,解决了(SnowFlake)雪花算法的坑,时钟回拨问题
其中最主要的就是 处理时钟回拨 ,生成的规则是传入服务器的唯一workId,根据workId生成id
代码
package com.example.demo;
/**
* @Author: rumenxiaobaidog
* @Date: 2020/12/15
* @Desc: 雪花算法生成唯一id 处理了时钟回拨的坑
*/
public class SnowFlakeWorkerUtils {
private volatile static SnowFlakeWorkerUtils snowFlakeWorkerInstance;
// 1位标识部分 - 41位时间戳部分 - 10位节点部分 12位序列号部分
/** 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 */
/**
* 起始的时间戳
*/
private final static long START_STMP = 1288834974657L;
/**
* 每一部分占用的位数
*/
// 序列号占用的位数
private final static long SEQUENCE_BIT = 12;
// 机器标识占用的位数
private final static long WORK_BIT = 10;
/**
* WORK_NUM最大值 1023
*/
private final static long MAX_WORK_NUM = -1L ^ (-1L << WORK_BIT);
/**
* SEQUENCE最大值 4095
*/
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
/**
* 每一部分向左的位移
*/
private final static long WORK_LEFT = SEQUENCE_BIT;
private final static long TIMESTMP_LEFT = WORK_LEFT + WORK_BIT;
private long workId;
private long sequence = 0L; //序列号
private long lastStmp = -1L; //上一次时间戳
/** 步长, 1024 */
private static long stepSize = 2 << 9;
/** 基础序列号, 每发生一次时钟回拨即改变, basicSequence += stepSize */
private long basicSequence = 0L;
private SnowFlakeWorkerUtils(long workId) {
if (workId > MAX_WORK_NUM || workId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
this.workId = workId;
}
private synchronized static SnowFlakeWorkerUtils initSnowFlakeWorker(long workId) {
snowFlakeWorkerInstance = new SnowFlakeWorkerUtils(workId);
return snowFlakeWorkerInstance;
}
public static SnowFlakeWorkerUtils getInstance(long workId) {
if (null == snowFlakeWorkerInstance) {
synchronized (SnowFlakeWorkerUtils.class) {
if (null == snowFlakeWorkerInstance) {
snowFlakeWorkerInstance = new SnowFlakeWorkerUtils(workId);
}
}
}
return snowFlakeWorkerInstance;
}
/**
* 产生下一个ID
*/
public synchronized long nextId() {
long currStmp = getNewstmp();
if (currStmp < lastStmp) {
return handleClockBackwards(currStmp);
}
if (currStmp == lastStmp) {
// 相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
// 同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
// 不同毫秒内,序列号置为 basicSequence
sequence = basicSequence;
}
lastStmp = currStmp;
return (currStmp - START_STMP) << TIMESTMP_LEFT // 时间戳部分
| workId << WORK_LEFT // 节点部分
| sequence; // 序列号部分
}
/**
* 处理时钟回拨
*/
private long handleClockBackwards(long currStmp) {
basicSequence += stepSize;
if (basicSequence == MAX_SEQUENCE + 1) {
basicSequence = 0;
currStmp = getNextMill();
}
sequence = basicSequence;
lastStmp = currStmp;
return (currStmp - START_STMP) << TIMESTMP_LEFT // 时间戳部分
| workId << WORK_LEFT // 节点部分
| sequence; // 序列号部分
}
private long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
}
private long getNewstmp() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
//workId 可根据不同的机器标识唯一的id
Long workId = 1L;
for (int i = 0; i < 100; i++) {
long nextId = SnowFlakeWorkerUtils.getInstance(workId).nextId();
System.out.println(nextId);
}
}
}
基于github开源的SnowFlake加以改进,处理了时钟回拨
特别感谢: https://github.com/beyondfengyu/SnowFlake/blob/master/SnowFlake.java
希望给大家提供帮助,谢谢