我們設計數據表的時候,一般都會有ID字段,ID的生成方法有多種,比如數據庫自增,UUID,雪花算法等。
考慮到以後數據的增長,分庫分表,分佈式等要求,我們選擇雪花算法來生成ID。
開發Web系統需要後端跟前端交互,前端JavaScript支持的最大整型是53位,超過53位會丟失精度,原版的雪花算法會超過53位,我們使用縮小版的雪花算法,把位數減小到53位。
原版Snowflake算法的極限是每毫秒的每一個節點生成4059個id值,也就是說每毫秒的極限是生成023*4059=4 152 357個id值 ,縮小後極限是每秒生成15*131071=1 966 065個分佈式id,夠我們在開發裏面的日常使用了。
package cn.gintone.asso.util;
/**
* @description:縮小版的雪花算法
* @author:Elon He
* @create:2020-10-06
*/
public class SnowflakeMini {
/**
* 開始時間截 (1970-01-01)
*/
private final static long twepoch = 0L;
/**
* 機器id,範圍是1到15
*/
private final static long workerId =1L;
/**
* 機器id所佔的位數,佔4位
*/
private final static long workerIdBits = 4L;
/**
* 支持的最大機器id,結果是15
*/
private final static long maxWorkerId = ~(-1L << workerIdBits);
/**
* 生成序列佔的位數
*/
private final static long sequenceBits = 15L;
/**
* 機器ID向左移15位
*/
private final static long workerIdShift = sequenceBits;
/**
* 生成序列的掩碼,這裏爲最大是32767 (1111111111111=32767)
*/
private final static long sequenceMask = ~(-1L << sequenceBits);
/**
* 時間截向左移19位(4+15)
*/
private final static long timestampLeftShift = 19L;
/**
* 秒內序列(0~32767)
*/
private static long sequence = 0L;
/**
* 上次生成ID的時間截
*/
private static long lastTimestamp = -1L;
/**
* 獲得下一個ID (該方法是線程安全的)
*
* @return SnowflakeId
*/
public static synchronized long nextId() {
//返回以秒爲單位的當前時間
long timestamp = timeGen();
//如果當前時間小於上一次ID生成的時間戳,說明系統時鐘回退過這個時候應當拋出異常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//藍色代碼註釋結束
//紅色代碼註釋開始
//如果是同一時間生成的,則進行秒內序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//秒內序列溢出
if (sequence == 0) {
//阻塞到下一個秒,獲得新的秒值
timestamp = tilNextMillis(lastTimestamp);
}
//時間戳改變,秒內序列重置
}
//紅色代碼註釋結束
//綠色代碼註釋開始
else {
sequence = 0L;
}
//綠色代碼註釋結束
//上次生成ID的時間截
lastTimestamp = timestamp;
//黃色代碼註釋開始
//移位並通過或運算拼到一起組成53 位的ID
return ((timestamp - twepoch) << timestampLeftShift)
| (workerId << workerIdShift)
| sequence;
//黃色代碼註釋結束
}
/**
* 阻塞到下一個秒,直到獲得新的時間戳
*
* @param lastTimestamp 上次生成ID的時間截
* @return 當前時間戳
*/
protected static long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以秒爲單位的當前時間
*
* @return 當前時間(秒)
*/
protected static long timeGen() {
return System.currentTimeMillis()/1000L;
}
}
創建兩個線程,同時測試ID獲取。
測試線程類A
package cn.gintone.asso;
import cn.gintone.asso.util.SnowflakeMini;
/**
* @description:線程A
* @author:Elon He
* @create:2020-10-06
*/
public class ThreadA extends Thread{
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
long id = SnowflakeMini.nextId();
System.out.println("A:"+id);
}
}
}
測試線程類B
package cn.gintone.asso;
import cn.gintone.asso.util.SnowflakeMini;
/**
* @description:線程B
* @author:Elon He
* @create:2020-10-06
*/
public class ThreadB extends Thread{
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
long id = SnowflakeMini.nextId();
System.out.println("B:"+id);
}
}
}
測試類(註釋掉的代碼是修改成靜態方法前的測試方法)
package cn.gintone.asso;
/**
* @description:雪花算法測試類
* @author:Elon He
* @create:2020-10-06
*/
public class TestSnowflake {
public static void main(String[] args) {
//不同線程使用同一個對象,不重複
// SnowflakeMini idWorker = new SnowflakeMini(0);
// ThreadA t1 = new ThreadA(idWorker);
// ThreadB t2 = new ThreadB(idWorker);
// t1.start();
// t2.start();
//不同線程使用不同對象,會重複
// SnowflakeMini idWorker1 = new SnowflakeMini(0);
// SnowflakeMini idWorker2 = new SnowflakeMini(0);
// ThreadA t1 = new ThreadA(idWorker1);
// ThreadB t2 = new ThreadB(idWorker2);
// t1.start();
// t2.start();
//nextId修改成靜態方法後測試,不重複
ThreadA t1 = new ThreadA();
ThreadB t2 = new ThreadB();
t1.start();
t2.start();
}
}
測試結果