大背景不讲,参考:https://segmentfault.com/a/1190000011282426#articleHeader5
小背景:我们的订单编号要求是 16 位,改造了一下雪花算法
*
* 参考Twitter Snowflake算法,按实际需求,做了部分修改,结构如下(每部分用-分开):
* 0000000000 - 10000000000000000000000000000000000000000 - 00 - 000 - 00000000
* 10位不使用,因为目的是为了最终生成16位整数,所以只使用后面的54bit
* 41位时间截(毫秒级),存储时间截的差值(当前时间截 - 开始时间截),41位的时间截,可以使用69年,且考虑到差值较小时,会生成不足16位的数字,因些需要选择一个合适的值
* 2位的集群ID,可以部署在4个集群
* 3位的节点ID,每个集群可以有8个节点
* 8位序列,毫秒内的计数,支持每个节点每毫秒产生256个ID序号
* 加起来刚好64位,为一个Long型
*/public class UniqueIdWorker {
/**
* 起始时间,用于调整位数
* 这里取值 2012-12-22 00:00:00
* 以41位表示毫秒,此方案可以使用到 2082-08-28 15:47:35,订单编号从15开头,
*/ private final long baseTimestamp = 1356105600000L;
/**
* 机器id所占的位数
*/
private final long workerIdBits = 3L;
/**
* 集群id所占的位数
*/
private final long clusterIdBits = 2L;
/**
* 支持的最大机器id
*/ private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/**
* 支持的最大集群id
*/ private final long maxClusterId = -1L ^ (-1L << clusterIdBits);
/**
* 序列在id中占的位数
*/
private final long sequenceBits = 8L;
/**
* 机器ID向左移位数
*/
private final long workerIdShift = sequenceBits;
/**
* 集群id向左移位数
*/
private final long clusterIdShift = sequenceBits + workerIdBits;
/**
* 时间截向左移位数
*/
private final long timestampLeftShift = sequenceBits + workerIdBits + clusterIdBits;
/**
* 生成序列的掩码
*/
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/**
* 工作机器ID
*/ private long workerId;
/**
* 集群ID
*/ private long clusterId;
/**
* 毫秒内序列
*/
private long sequence = 0L;
/**
* 上次生成ID的时间截
*/
private long lastTimestamp = -1L;
/**
* 构造函数
*
* @param workerId
* @param clusterId
*/
public UniqueIdWorker(Long workerId, Long clusterId) {
Preconditions.checkArgument(null != workerId && workerId > 0 && workerId < maxWorkerId, "Invalid workerId");
Preconditions.checkArgument(null != clusterId && clusterId > 0 && clusterId < maxClusterId, "Invalid clusterId");
this.workerId = workerId;
this.clusterId = clusterId;
}
/**
* 获得下一个ID
* * @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
//系统时钟回退,抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Failed to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//同一毫秒内顺序递增
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒内序列溢出
if (sequence == 0) {
//阻塞到下一个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//时间戳改变重置为0
else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - baseTimestamp) << timestampLeftShift)
| (clusterId << clusterIdShift)
| (workerId << workerIdShift)
| sequence;
}
/**
* 阻塞到下一个毫秒的时间戳并返回
*
* @param lastTimestamp
* @return
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回当前毫秒时间戳
*
* @return
*/
private long timeGen() {
return System.currentTimeMillis();
}
/**
* 根据订单ID反向解析内容
*
* @param id
* @return
*/
public String parseId(Long id) {
if (null == id) {
return "";
}
return String.format("sequence: %d, workerId: %d, clusterId: %d, timestamp: %d\n", ((id) & ~(-1L << sequenceBits))
, ((id >> (workerIdShift)) & ~(-1L << (workerIdBits)))
, ((id >> clusterIdShift) & ~(-1L << clusterIdBits))
, ((id >> timestampLeftShift) + baseTimestamp));
}
}
解释
41 位时间戳能用几年?
@Test
public void test2() {
String minTimeStampStr = "00000000000000000000000000000000000000000";
long minTimeStamp = new BigInteger(minTimeStampStr, 2).longValue();
String maxTimeStampStr = "11111111111111111111111111111111111111111";
long maxTimeStamp = new BigInteger(maxTimeStampStr, 2).longValue();
long oneYearMills = 1L * 1000 * 60 * 60 * 24 * 365;
System.out.println((maxTimeStamp - minTimeStamp) / oneYearMills);
}
结果是 69
前 41 位最小值
如果前 41 位太小,结果可能不满 16 位。
计算 1000_0000_0000_0000L 的前 41 位
@Test
public void test1() {
String str = Long.toBinaryString(
1000_0000_0000_0000L);//00001110001101011111101010010011000110100_0000000000000
// String str = Long.toBinaryString(9999_9999_9999_9999L);//10001110000110111100100110111111000000111_1111111111111
System.out.println(str);
int needZero = 54 - str.length();
str = StringUtils.repeat("0", needZero) + str;
char[] chars = str.toCharArray();
System.out.println("length :" + chars.length);
for (int i = 0; i < chars.length; i++) {
if (i != 0 && i % 41 == 0) {
System.out.print("_");
}
System.out.print(chars[i]);
}
}
起始时间计算
用当前时间戳减去前 41 位最小值,得到的时间就是起始时间,如果需要开头从 15 或者 20 开始,也可以自行计算。
@Test
public void test3() {
long curTimeStamp = System.currentTimeMillis();
//也可以用下面的
// long curTimeStamp1 = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()
// .toEpochMilli();
System.out.println(curTimeStamp);
// System.out.println(curTimeStamp1);
//差值最小为00001110001101011111101010010011000110100
String diffTimeStampStr = "00001110001101011111101010010011000110100";
long diffTimeStamp = new BigInteger(diffTimeStampStr, 2).longValue();
long minTimeStamp = curTimeStamp - diffTimeStamp;
LocalDateTime minDateTime = LocalDateTime
.ofInstant(Instant.ofEpochMilli(minTimeStamp), ZoneId.systemDefault());
System.out.println(minDateTime);//2015-02-15T17:59:14.079
}
-1L ^ (-1L << workerIdBits)求最大机器 id
//-1 的二进制原码1000 0001,反码 - 1111 1111
//-1 << 3也就是-8的二进制 1000 1000 反码- 1111 1000
// 1111 1111 ^ 1111 1000 = 0000 0111
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于