分散式應用序列號生成
工程中先後考慮了幾種業務號生成的方式,最終採用 snowFlake 演算法:
以下是自己批註的一些東東,mark 一下:
相對於 引用的原文, 這裡將 SnowFlake 以單例的形式, 注入到 Spring 容器管理。
單例 和 加鎖,私以為,只有單例 和 加鎖,才能真正保證獲取 Id 的執行緒是安全地。
將單例 注入 Spring 中的方法,可以參考:
以下是原始碼
package com.sinosoft.app.common.util;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 整個snowFlake演算法基於 二進位制運算
* snowFlake 序列號,佔位 long 整形的 64 位 二進位制
* 第一組:前 44 位 儲存 timestamp
* 第二組:資料中心編碼 3 位 (對應 2^3 = 8 組)
* 第三組:機器編碼 7 位 (2^7=32臺分散式應用)
* 第四組: 十位序列號(2^10 = 1024 ) 一毫秒 一個節點應用,支援產生 1024 個不重複的序列號,如果有重複的, 則該序列號延遲到下一個毫秒
* @author pzj
*
*/
@Component
public class SnowFlake {
// 起始的時間戳
private final static long START_STMP = 1531971653531L;
// 每一部分佔用的位數,就三個
private final static long SEQUENCE_BIT = 10;// 序列號佔用的位數
private final static long MACHINE_BIT = 7; // 機器標識佔用的位數
private final static long DATACENTER_BIT = 3;// 資料中心佔用的位數
// 每一部分最大值
private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
// 每一部分向左的位移
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
/* @Value("${datacenterId}")
private String datacenterIdStr; // 資料中心
@Value("${machineId}")
private String machineIdStr; // 機器標識
*/
@Value("${datacenterId}")
private long datacenterId;
@Value("${machineId}")
private long machineId;
private static SnowFlake snowFlake= new SnowFlake();
private long sequence = 0L; // 序列號
private long lastStmp = -1L;// 上一次時間戳
private SnowFlake() {
if (this.datacenterId > MAX_DATACENTER_NUM || this.datacenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (this.machineId > MAX_MACHINE_NUM || this.machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
}
public static SnowFlake getSnowFlake(){
return snowFlake;
}
//產生下一個ID
public synchronized long nextId() {
long currStmp = getNewstmp();
if (currStmp < lastStmp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (currStmp == lastStmp) {
//if條件裡表示當前呼叫和上一次呼叫落在了相同毫秒內,只能通過第三部分,序列號自增來判斷為唯一,所以+1.
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列數已經達到最大,只能等待下一個毫秒
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
//不同毫秒內,序列號置為0
//執行到這個分支的前提是currTimestamp > lastTimestamp,說明本次呼叫跟上次呼叫對比,已經不再同一個毫秒內了,這個時候序號可以重新回置0了。
sequence = 0L;
}
lastStmp = currStmp;
//就是用相對毫秒數、機器ID和自增序號拼接
return (currStmp - START_STMP) << TIMESTMP_LEFT //時間戳部分
| datacenterId << DATACENTER_LEFT //資料中心部分
| machineId << MACHINE_LEFT //機器標識部分
| sequence; //序列號部分
}
private long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
}
private long getNewstmp() {
return System.currentTimeMillis();
}
@PostConstruct
public void init() {
snowFlake = this;
snowFlake.datacenterId = this.datacenterId;
snowFlake.machineId = this.machineId;
}
}
對於二進位制腦補困難的同學,可以嘗試計算器上的這個功能,很好用: