提到全局ID生成器,市面上已有很多成熟的解决方案,比如:MySQL的自增神器、jdk的UUID、Redis的id、zookeeper的id、Twitter的snowflake、美团的Leaf等等。
本文主要是自己想倒腾一下^_^~
下面进入正文,实现的ID有两个功能,防止时间的回拨,ID自增。
直接上代码
import cn.cosmosx.base.helper.SpringContextHolder;
import cn.cosmosx.base.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
/**
* ID生成器
*/
@Slf4j
@Component
public final class IDGenerator implements InitializingBean {
/**
* 生成全局流水号
*/
public static String generateGlobalSerial() {
return IDRule.GLOBAL_SERIAL.generate();
}
/**
* 生成资源请求ID
*/
public static String generateRequestId() {
return IDRule.REQUEST_ID.generate();
}
/**
* 生成短ID
*/
public static String generateMiniId() {
return IDRule.MINI_ID.generate();
}
/**
* 初始化缓存中的流水号
*/
public static void initialize(RedisService redisService) {
if (redisService == null) {
log.error("RedisService bean is null, not initialize IDRules");
return;
}
for (IDRule rule : IDRule.values()) {
Integer serialSuffix = redisService.get(rule.name());
rule.getSuffix().set(serialSuffix == null ? 0 : serialSuffix);
log.info("Initialize {}: {}", rule.name(), rule.getSuffix().get());
}
}
/**
* 持久化
*/
public static void persist(RedisService redisService) {
if (redisService == null) {
log.error("RedisService bean is null, not persist IDRules");
return;
}
for (IDRule rule : IDRule.values()) {
redisService.set(rule.name(), rule.getSuffix().get());
log.info("Persist {}: {}", rule.name(), rule.getSuffix().get());
}
}
@Override
public void afterPropertiesSet() {
IDGenerator.initialize(SpringContextHolder.getBean(RedisService.class));
}
}
核心代码
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.text.DecimalFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
/**
* ID生成规则
*/
@Slf4j
@Getter
enum IDRule {
/**
* 全局流水号标识(1位“G”)+系统ID(2位)+时间戳(14位)+序号(7位)24位长度
*/
GLOBAL_SERIAL("全局流水号", "GQS", new DecimalFormat("0000000"), 9999999),
/**
* 资源请求ID:"REQUEST_ID" + 17位时间戳 + 6位自增序列号
*/
REQUEST_ID("资源请求ID", "REQUEST", new DecimalFormat("000000"), 999999, Unit.MILLIS_SECONDS),
/**
* 资源请求ID:"MI" + 23位时间戳 + 2位自增序列号
*/
MINI_ID("短ID", "MI", new DecimalFormat("000"), 999),
;
/**
* 锁
*/
private final ReentrantLock lock;
/**
* 描述
*/
private final String name;
/**
* 前缀
*/
private final String prefix;
/**
* 后缀
*/
private final AtomicInteger suffix;
/**
* 后缀格式化
*/
private final DecimalFormat suffixFormat;
/**
* 后缀最大值
*/
private final int suffixMax;
/**
* 时间戳格式化精确单位
*/
private final Unit unit;
/**
* 上一个时间轮(单位:ms)
*/
private LocalDateTime lastTimeWheel;
/**
* 步长,默认为单机,步长为1。(服务器数量与步长保持一致。如2台服务器,步长为2)
*/
private int step = 1;
IDRule(String name, String prefix, DecimalFormat suffixFormat, int suffixMax) {
// 默认时间戳到秒级别,共14位
this(name, prefix, suffixFormat, suffixMax, Unit.SECONDS);
}
IDRule(String name, String prefix, DecimalFormat suffixFormat, int suffixMax, Unit unit) {
this(new ReentrantLock(), name, prefix, new AtomicInteger(0), suffixFormat, suffixMax, unit);
}
IDRule(ReentrantLock lock, String name, String prefix, AtomicInteger suffix, DecimalFormat suffixFormat, int suffixMax, Unit unit) {
this.lock = lock;
this.name = name;
this.prefix = prefix;
this.suffix = suffix;
this.suffixFormat = suffixFormat;
this.suffixMax = suffixMax;
this.unit = unit;
}
/**
* 取当前服务器时间戳(ms)
*/
LocalDateTime ts() {
return LocalDateTime.now();
}
/**
* 生成最终生成的序列号
*/
public final String generate() {
lock.lock();
try {
int suffix = suffix();
String ts = unit.getFormat().format(handleTs(suffix));
return prefix + ts + suffixFormat.format(suffix);
} finally {
lock.unlock();
}
}
/**
* 处理时间戳
*
* @param suffix - 后缀
* @return - newTs
*/
private LocalDateTime handleTs(int suffix) {
// 初始化时间戳
LocalDateTime ts = initTs(suffix);
// 时钟回拨处理
if (isClockCallback(ts)) {
ts = ts();
}
// 重置时间轮
setLastTimeWheel(ts);
return ts;
}
/**
* 初始化时间戳
*
* @param suffix - 自增序列号
*/
private LocalDateTime initTs(int suffix) {
LocalDateTime ts = ts();
if (getLastTimeWheel() == null) {
setLastTimeWheel(ts);
return ts;
}
// 时间戳不变,后缀序列号归零处理,延迟1个单位时间
if (suffix == step && unit.getFormat().format(getLastTimeWheel()).equals(unit.getFormat().format(ts))) {
log.warn("{} auto-increment-serial is reset:{}.", this.name(), suffix);
sleepNanoSeconds(getUnit());
ts = ts();
}
return ts;
}
/**
* 时钟是否回拨
* 承受时钟回拨的误差范围最大为500ms
*
* @param newTs - 当前服务器时间戳
*/
private boolean isClockCallback(LocalDateTime newTs) {
final LocalDateTime last = getLastTimeWheel();
if (newTs.isAfter(last) || newTs.isEqual(last)) {
return false;
}
long diffMs = Duration.between(last, newTs).toMillis();
if (diffMs - 500L <= 0) {
log.warn("{} clock time is callback, differ:{}ms.", this.name(), suffix);
try {
TimeUnit.MILLISECONDS.sleep(diffMs);
} catch (InterruptedException ignored) {
}
return true;
}
throw new RuntimeException("Current server clock is callback, differ:" + diffMs + "ms.");
}
/**
* 生成自增序列号
*/
private int suffix() {
int s = suffix.addAndGet(step);
if (s > suffixMax) {
// 重置自增序列号
suffix.set(0);
s = suffix.addAndGet(step);
}
return s;
}
private void sleepNanoSeconds(Unit unit) {
// 睡眠纳秒数
long nanosSeconds;
switch (unit) {
case SECONDS:
nanosSeconds = (long) Math.pow(10, 9);
break;
case MILLIS_SECONDS:
nanosSeconds = (long) Math.pow(10, 6);
break;
case MICROS_SECONDS:
nanosSeconds = (long) Math.pow(10, 3);
break;
default:
nanosSeconds = 1L;
break;
}
try {
// 睡眠 range ns
TimeUnit.NANOSECONDS.sleep(nanosSeconds);
} catch (InterruptedException ignored) {
}
}
void setLastTimeWheel(LocalDateTime millisSeconds) {
this.lastTimeWheel = millisSeconds;
}
/**
* 设置自增序列号步长,服务器数量与步长保持一致。如2台服务器,步长为2。以此类推
*
* @param step - 步长
*/
public void setStep(int step) {
this.step = step;
}
/**
* 时间戳格式化精确单位
*/
@Getter
@AllArgsConstructor
enum Unit {
SECONDS(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")),
MILLIS_SECONDS(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")),
MICROS_SECONDS(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSSSSS")),
NANOS_SECONDS(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSSSSSSSS"));
private final DateTimeFormatter format;
}
}
露干!