Java實現雪花算法的示例代碼
一、介紹
SnowFlow算法是Twitter推出的分布式id生成算法,主要核心思想就是利用64bit的long類型的數字作為全局的id。在分布式系統(tǒng)中經常應用到,并且,在id中加入了時間戳的概念,基本上保持不重復,并且持續(xù)一種向上增加的方式。
在這64bit中,其中``第一個bit是不用的,然后用其中的41個bit作為毫秒數,用10bit作為工作機器id,12bit`作為序列號.具體如下圖所示:

第一個部分:0,這個是個符號位,因為在二進制中第一個bit如果是1的話,那么都是負數,但是我們生成的這些id都是正數,所以第一個bit基本上都是0
第二個部分:41個bit,代表的是一個時間戳,41bit可以表示的數字多達$2^{41} $-1,也可以表示2^{41}-1個毫秒值,基本上差不多是69年。
第三個部分:5個bit 表示的是機房id。
第四個部分:5個bit 表示的是機器id。
第五個部分:12個bit 表示的是機房id,表示的序號,就是某個機房某臺機器上這一毫秒內同時生成的 id 的序號,0000 00000000,如果是同一毫秒,那么這個雪花值就會遞增
簡單來說,你的某個服務假設要生成一個全局唯一 id,那么就可以發(fā)送一個請求給部署了 SnowFlake 算法的系統(tǒng),由這個 SnowFlake 算法系統(tǒng)來生成唯一 id。
這個算法可以保證說,一個機房的一臺機器上,在同一毫秒內,生成了一個唯一的 id??赡芤粋€毫秒內會生成多個 id,但是有最后 12 個 bit 的序號來區(qū)分開來。
下面我們就來簡單看下這個算法的代碼實現部分。
總之就是用一個64bit的數字中各個bit位置來設置不同的標志位
二、代碼實現
package com.lhh.utils;
/**
* @author liuhuanhuan
* @version 1.0
* @date 2022/2/21 22:33
* @describe Twitter推出的分布式唯一id算法
*/
public class SnowFlow {
//因為二進制里第一個 bit 為如果是 1,那么都是負數,但是我們生成的 id 都是正數,所以第一個 bit 統(tǒng)一都是 0。
//機器ID 2進制5位 32位減掉1位 31個
private long workerId;
//機房ID 2進制5位 32位減掉1位 31個
private long datacenterId;
//代表一毫秒內生成的多個id的最新序號 12位 4096 -1 = 4095 個
private long sequence;
//設置一個時間初始值 2^41 - 1 差不多可以用69年
private long twepoch = 1585644268888L;
//5位的機器id
private long workerIdBits = 5L;
//5位的機房id;?!?
private long datacenterIdBits = 5L;
//每毫秒內產生的id數 2 的 12次方
private long sequenceBits = 12L;
// 這個是二進制運算,就是5 bit最多只能有31個數字,也就是說機器id最多只能是32以內
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 這個是一個意思,就是5 bit最多只能有31個數字,機房id最多只能是32以內
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private long workerIdShift = sequenceBits;
private long datacenterIdShift = sequenceBits + workerIdBits;
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
// -1L 二進制就是1111 1111 為什么?
// -1 左移12位就是 1111 1111 0000 0000 0000 0000
// 異或 相同為0 ,不同為1
// 1111 1111 0000 0000 0000 0000
// ^
// 1111 1111 1111 1111 1111 1111
// 0000 0000 1111 1111 1111 1111 換算成10進制就是4095
private long sequenceMask = -1L ^ (-1L << sequenceBits);
//記錄產生時間毫秒數,判斷是否是同1毫秒
private long lastTimestamp = -1L;
public long getWorkerId(){
return workerId;
}
public long getDatacenterId() {
return datacenterId;
}
public long getTimestamp() {
return System.currentTimeMillis();
}
public SnowFlow() {
}
public SnowFlow(long workerId, long datacenterId, long sequence) {
// 檢查機房id和機器id是否超過31 不能小于0
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(
String.format("worker Id can't be greater than %d or less than 0",maxWorkerId));
}
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;
this.sequence = sequence;
}
// 這個是核心方法,通過調用nextId()方法,
// 讓當前這臺機器上的snowflake算法程序生成一個全局唯一的id
public synchronized long nextId() {
// 這兒就是獲取當前時間戳,單位是毫秒
long timestamp = timeGen();
// 判斷是否小于上次時間戳,如果小于的話,就拋出異常
if (timestamp < lastTimestamp) {
System.err.printf("clock is moving backwards. Rejecting requests until %d.", lastTimestamp);
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
lastTimestamp - timestamp));
}
// 下面是說假設在同一個毫秒內,又發(fā)送了一個請求生成一個id
// 這個時候就得把seqence序號給遞增1,最多就是4096
if (timestamp == lastTimestamp) {
// 這個意思是說一個毫秒內最多只能有4096個數字,無論你傳遞多少進來,
//這個位運算保證始終就是在4096這個范圍內,避免你自己傳遞個sequence超過了4096這個范圍
sequence = (sequence + 1) & sequenceMask;
//當某一毫秒的時間,產生的id數 超過4095,系統(tǒng)會進入等待,直到下一毫秒,系統(tǒng)繼續(xù)產生ID
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
// 這兒記錄一下最近一次生成id的時間戳,單位是毫秒
lastTimestamp = timestamp;
// 這兒就是最核心的二進制位運算操作,生成一個64bit的id
// 先將當前時間戳左移,放到41 bit那兒;將機房id左移放到5 bit那兒;將機器id左移放到5 bit那兒;將序號放最后12 bit
// 最后拼接起來成一個64 bit的二進制數字,轉換成10進制就是個long型
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) | sequence;
}
/**
* 當某一毫秒的時間,產生的id數 超過4095,系統(tǒng)會進入等待,直到下一毫秒,系統(tǒng)繼續(xù)產生ID
* @param lastTimestamp
* @return
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
//獲取當前時間戳
private long timeGen(){
return System.currentTimeMillis();
}
/**
* main 測試類
* @param args
*/
public static void main(String[] args) {
// System.out.println(1&4596);
// System.out.println(2&4596);
// System.out.println(6&4596);
// System.out.println(6&4596);
// System.out.println(6&4596);
// System.out.println(6&4596);
SnowFlow snowFlow = new SnowFlow(1, 1, 1);
for (int i = 0; i < 22; i++) {
System.out.println(snowFlow.nextId());
// }
}
}
}
三、算法優(yōu)缺點
優(yōu)點:
(1)高性能高可用:生成時不依賴于數據庫,完全在內存中生成。
(2)容量大:每秒中能生成數百萬的自增ID。
(3)ID自增:存入數據庫中,索引效率高。
缺點:
依賴與系統(tǒng)時間的一致性,如果系統(tǒng)時間被回調,或者改變,可能會造成id沖突或者重復(時鐘重播造成的id重復問題)
到此這篇關于Java實現雪花算法的示例代碼的文章就介紹到這了,更多相關Java雪花算法內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用stream的Collectors.toMap()方法常見的問題及解決
這篇文章主要介紹了使用stream的Collectors.toMap()方法常見的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03

