long 雪花算法_雪花算法
小背景:我們的訂單編號要求是 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
總結
以上是生活随笔為你收集整理的long 雪花算法_雪花算法的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: js导出的xlsx无法打开_js-xls
- 下一篇: python设计拼图小游戏_教你用Pyt
