分布式系统ID生成方案
1.雪花算法
/**
* 一种分布式id生成算法
*/
public class SnowFlake {
/**
* 起始时间戳
*/
private static final long START_TIMESTAMP = 1575129600000L;
/**
* 时间戳所占位数,设置为43位,最大可表示时间间隔约为378年(距离START_TIME所设置时间)
*/
private static final long TIME_BIT = 43;
/**
* 机器编号所占位数,设置为8位,表示最多可容纳256台机器
*/
private static final long MACHINE_BIT = 8;
/**
* ID所占位数,设置为12位,表示每毫秒可最多产生的ID数量为4096个
*/
private static final long SEQUENCE_BIT = 12;
/**
* 机器编号最大值
*/
private final static long MAX_MACHINE = ~(-1L << MACHINE_BIT);
/**
* ID序号最大值
*/
private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
/**
* 机器编号的偏移量
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
/**
* 时间戳的偏移量
*/
private final static long TIME_LEFT = SEQUENCE_BIT + MACHINE_BIT;
/**
* 当前机器编号
*/
private long machineId;
/**
* 当前序号
*/
private long sequence = 0L;
/**
* 上一次时间戳
*/
private long lastTimestamp;
public SnowFlake(long machineId) {
if (machineId < 0 || machineId > MAX_MACHINE) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.machineId = machineId;
this.lastTimestamp = getCurrentTimestamp();
}
public synchronized long nextId() {
long currentTimestamp = getCurrentTimestamp();
if (currentTimestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards, Refusing to generate id");
}
if (currentTimestamp == lastTimestamp) {
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0L) {
//已超过最大值,阻塞至下一秒
currentTimestamp = getNextTimeMillis(currentTimestamp);
}
} else {
sequence = 0L;
}
this.lastTimestamp = currentTimestamp;
return (currentTimestamp - START_TIMESTAMP) << TIME_LEFT | machineId << MACHINE_LEFT | sequence;
}
/**
* 获取当前时间戳(毫秒)
*/
private long getCurrentTimestamp() {
return System.currentTimeMillis();
}
/**
* 阻塞直至下一毫秒
*/
private long getNextTimeMillis(long preMills) {
long cMills = getCurrentTimestamp();
while (cMills <= preMills) {
cMills = getCurrentTimestamp();
}
return cMills;
}
}
该方法生成一个长整形数字ID,同一节点产生的ID单调递增。但是在分布式部署的场景下,需要给每一台机器分配一个唯一的workId。
2.数据库
3.随机数+时间戳转32进制
import java.util.concurrent.ThreadLocalRandom;
public class IdUtil {
//前七位为随机字符串,后9位为毫秒时间戳
public static String getId() {
ThreadLocalRandom random = ThreadLocalRandom.current();
//保证生成的随机数字转换成32进制字符串时长度控制在3位
long num1 = random.nextInt(30000) + 2000;
//保证生成的随机数字转换成32进制字符串时长度控制在4位
long num2 = random.nextInt(1000000) + 40000;
//转换成32进制字符串时长度在9位
long ts = System.currentTimeMillis() * 2;
return to32ModStr(num1) + to32ModStr(num2) + to32ModStr(ts);
}
//32个字符元素,移除了4个容易和数字混淆的字母 g l o q
private final static char[] digits = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
'h', 'i', 'j', 'k', 'm', 'n', 'p', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
};
//长整型数字val转32进制字符串,自定义字符串,不是标准32进制字符顺序
private static String to32ModStr(long val) {
int mag = Long.SIZE - Long.numberOfLeadingZeros(val);
int cl = Math.max(((mag + 4) / 5), 1);
char[] buf = new char[cl];
int radix = 1 << 5;
int mask = radix - 1;
do {
buf[--cl] = digits[((int) val) & mask];
val >>>= 5;
} while (val != 0 && cl > 0);
return new String(buf);
}
}
该方法生成16位的字符串ID,前7位字符串为随机数字,后9位字符串为时间戳(精确到毫秒)。
前7位字符串被拆分为3位和4位两部分。如果不拆分,7位32进制字符已经超过了int范围,而且为了保证长度为7位,容易导致前几位字符变化范围小。
该方法产生的ID适用于分布式环境下的日志TraceId。