首頁>技術>

01 演算法介紹

Snowflake是Twitter開源的分散式ID生成演算法,結果是一個19位的Long型的ID。其核心思想是:使用41bit作為毫秒數,10bit作為機器的ID,12bit作為毫秒內的流水號(即每個節點在每毫秒可以產生 4096 個 ID),最後還有一個符號位,永遠是0。

佈局如下圖所示:

二進位制字串位(64位):

0101111001101110110111100011011101011110000000000000000000000000

該演算法的優缺點在網上很容易找到。

優點:

1、整體呈遞增趨勢

2、不依賴第三方系統,穩定性更高

3、可以根據自身業務特性分配bit位

缺點:

1、嚴重依賴時鐘

Snowflake演算法使用時間戳,個人認為是由於時間戳為全域性整體呈遞增趨勢,在防重上區別性比較大,同時方便獲取。

02 方案介紹

今天我主要介紹的是一種時間回撥時的解決方案:

回到snowflake演算法結構,仔細分析會發現:

1、10位的workId屬於自定義

2、12位的順序號主要是高併發

3、41位的時間戳本質為時間的差值,並非一定要求為當前時間。比如:System.currentTimeMillis(), 其實質為當前時間距離1970-01-01的時間差值的毫秒數。

實質上時間戳位置也可以是當前時間 - 基線時間(timeEpoch)計算之後的時間差值。而解決時間回撥的問題,入手點便在當前時間。雖然申明為當前時間,其實際上可以為任意一個大於基線時間的時間,只要保證隨著時間推移,整體遞增,且全域性唯一。

比如41位的時間戳的值為:

41位的時間戳 = 當前基礎時間 - 基線時間。

當前基礎時間 = 當前系統時間 - 時鐘回撥緩衝時間(比如1年 = 365 * 24 * 3600 * 1000L)。

上一次訪問時間 = 上一次訪問的基礎時間。

上一次訪問時間 大於 當前基礎時間 ,表示系統時間已經回撥。

此時透過調整時鐘回撥緩衝時間,修復當前基礎時間

時鐘回撥調整的幅度 = 上一次訪問時間 - 發生時鐘回撥之後的系統時間

當前基礎時間 = 當前系統時間 -( 時間回撥緩衝時間 - 時鐘回撥調整的幅度 )

修復示例圖:

注意:

1、方案中的的“上一次訪問時間”需要在當前節點持久化至檔案或者可持久化的位置

2、可修復的差值 = 上一次訪問時間 - 發生時鐘回撥之後的系統時間

從圖中示例可以看出,正常情況下,“時鐘回撥快取時間”為365天,如果發生時鐘回撥1天,可修復的差值 = 1,“時鐘回撥快取時間”調整為364(天) = 365(天) - 1(天)

如果時間回撥快取時間等於1年時,就表示系統執行時,時間回撥最大的時間為1年。

反饋問題:

1、我為什麼自定義基線時間即時間紀元。

經過測試,我發現時間差值在2000年左右才可以保證生成的ID為整數,如果超過則會產生負數,我修改時間紀元,主要為了延長使用的時間

2、上述時鐘快取時間為什麼是1年

時鐘快取時間可以自定義,1年只是我當前的使用值

程式碼如下:

// 獲取snowflake演算法計算之後的值public synchronized long getId() {        long timestamp = currentBaseTime();        if (timestamp < lastTimestamp) {            long offset = lastTimestamp - timestamp;            //毫秒級的時間倒退,直接等待            if (offset <= 5) {                try {                    wait(offset << 1);                } catch (Exception ex) {                    logger.error("wait={} 異常", offset);                }            } else {                //超過5ms的時間倒退,則直接修復                this.fixStepMills = offset;            }            timestamp = currentBaseTime();            //此處為兩次校驗,提高準確性            if (timestamp < lastTimestamp) {                this.fixStepMills = lastTimestamp - timestamp;                timestamp = currentBaseTime();            }        }        //最後的時間戳與當前時間相等        if (lastTimestamp == timestamp) {            sequence = (sequence + 1) & sequenceMask;            if (sequence == 0) {                sequence = random.nextInt(100);                timestamp = tilNextTimestamp(lastTimestamp);            }        } else {            sequence = random.nextInt(100);        }        this.lastTimestamp = timestamp;        return (timestamp - timeEpoch) << timestampShift | workId << workIdShift | sequence;}//獲取當前基礎時間private long currentBaseTime() {        // baseBackupMills:時間回撥緩衝時間        // fixStepMills:待修復的時間,即時鐘回撥的時間差值        long baseTimeEpoch = baseBackupMills - fixStepMills;        if (baseTimeEpoch <= 0) {            throw new IllegalArgumentException("time back to long");        }        LocalDateTime currentTime = getCurrentTime();        if (Objects.isNull(currentTime)) {            currentTime = LocalDateTime.now();        }        return currentTime.minusSeconds(baseTimeEpoch / 1000).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();}

9
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 編譯安裝和包管理器安裝有什麼優勢和劣勢?