雪花算法的簡介:
雪花算法用來實現全局唯一ID的業務主鍵,解決分庫分表之后主鍵的唯一性問題,所以就單從全局唯一性來說,其實有很多的解決方法,比如說UUID、數據庫的全局表的自增ID
但是在實際的開發過程中,我們的id除了唯一性以外,還需要去滿足有序遞增,高性能,高可用,以及需要時間戳等這樣一些特征,而雪花算法就是一個比較符合這個一類特征的全局唯一算法。
雪花算法結構的詳解:
它是一個通過64個bit位 組成的一個long類型的數字,可以將它分為四個部分,根據這四個部分的規則,生成對應的bit位的一個數據,然后組裝在一起,形成一個全局的唯一id。
第一部分:是一個bit:這個是正負號,正常情況下為零,通常無意義
1)不用 1bit:是不用的
因為二進制里第一個bit位如果是1,那么都是復數,但是我們生成的id都是正數,所以第一個bit統一都是0
第二部分:是41個bit:表示的是時間戳
2)時間戳 41bit:表示的是時間戳,單位是毫秒
41bit表示的數字多達2^41-1,也就是可以標識2^41-1個毫秒值,換算成年表示就是69年的時間。
第三、四部分:是5+5個bit:表示的是機房id以及機器id、
3)+4)工作機器Id 10bit:記錄工作機器的id,表示的是這個服務最多可以部署在2^10臺機器上,也就是1024臺機器。
但是10bit里5個bit代表機房id,5個bit代表機器id。意思就是最多代表2^個機房(32個機房),每個機房可以代表2^5和機器(32臺機器),也可以根據實際情況確定
第五部分:是12個bit:表示的序號,就是某個機房中某個機器上這一毫秒內同時生成的id的序號,0000 0000 0000
12bit可以代表的最大正整數是2^12-1=4096,也就是說可以用這個12bit代表的數字來區分同一個毫秒內的4096個不同的id。
源碼:
public class SnowFlakeUtil01 {// 起始時間戳 (可以自定義)private final long twepoch = 1288834974657L;// 機器ID所占的位數private final long workerIdBits = 5L;// 數據中心ID所占的位數private final long datacenterIdBits = 5L;// 支持的最大機器ID,結果是31 (這個移位算法可以計算最大值:-1L ^ (-1L << workerIdBits))private final long maxWorkerId = -1L ^ (-1L << workerIdBits);// 支持的最大數據中心ID,結果是31private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);// 序列在ID中占的位數private final long sequenceBits = 12L;// 機器ID左移位數private final long workerIdShift = sequenceBits;// 數據中心ID左移位數private final long datacenterIdShift = sequenceBits + workerIdBits;// 時間戳左移位數private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;// 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095)private final long sequenceMask = -1L ^ (-1L << sequenceBits);// 工作機器ID(0~31)private long workerId;// 數據中心ID(0~31)private long datacenterId;// 毫秒內序列(0~4095)private long sequence = 0L;// 上次生成ID的時間戳private long lastTimestamp = -1L;// 構造函數public SnowFlakeUtil01(long workerId, long datacenterId) {// 檢查workerId是否在合法范圍內if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}// 檢查datacenterId是否在合法范圍內if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));}this.workerId = workerId;this.datacenterId = datacenterId;}/*** 獲得下一個ID (該方法是線程安全的)* @return SnowflakeId*/public 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) {// 如果毫秒相同,則從0遞增生成序列號sequence = (sequence + 1) & sequenceMask;// 毫秒內序列溢出if (sequence == 0) {// 阻塞到下一個毫秒,獲得新的時間戳timestamp = tilNextMillis(lastTimestamp);}}// 時間戳改變,毫秒內序列重置else {sequence = 0L;}// 上次生成ID的時間戳lastTimestamp = timestamp;// 移位并通過或運算拼到一起組成64位的IDreturn ((timestamp - twepoch) << timestampLeftShift) // 時間戳部分| (datacenterId << datacenterIdShift) // 數據中心部分| (workerId << workerIdShift) // 機器ID部分| sequence; // 序列號部分}// 阻塞到下一個毫秒,直到獲得新的時間戳protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}// 返回當前時間,以毫秒為單位protected long timeGen() {return System.currentTimeMillis();}// public static void main(String[] args) {
// SnowFlakeUtil snowFlakeUtil = new SnowFlakeUtil(0, 0);
// for (int i = 0; i < 100; i++) {
// long id = snowFlakeUtil.nextId();
// System.out.println(id);
// }
// }
}