1. 程式人生 > >SnowflakeId雪花ID演算法,分散式自增ID應用

SnowflakeId雪花ID演算法,分散式自增ID應用

概述


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

特點:

  1. 作為ID,肯定是唯一的;
  2. 自增,依賴時間戳生成,序列號有序遞增;
  3. 支援非常大的業務ID生成,最大支援2^10=1024個業務節點,同一個節點一毫秒最多生成2^12=4096個ID,41位毫秒級時間可以使用(2^41 - 1)/(1000*60*60*24*365)=69.73,大約70年;
  4. 實現簡單,不依賴於其他第三方元件,甚至都不需要任何import。

結果是一個Long型的ID,64位,結構圖如下:

 

  1. 第1位固定是0,表示正數;
  2. 第2-42共41位表示時間戳,當前時間的時間戳減去開始時間的時間戳;
  3. 業務節點ID,每個節點固定的值;
  4. 毫秒內的序列號。

實現


 

清楚了結構後,就比較好實現了。

41bit-時間戳

當前時間的時間戳減去開始時間的時間戳,左移22位

(ts - beginTs) << timestampLeftOffset

10bit-工作機器ID

自定義的業務節點ID,固定的值,左移12位

workerId << workerIdLeftOffset

12bit-序列號

毫秒內序列號,以此遞增,如果溢位就阻塞到下一秒從0開始計數

// 同一時間內,則計算序列號
if (ts == lastTimestamp) {
    // 序列號溢位
    if (++sequence > maxSequence) {
        ts = tilNextMillis(lastTimestamp);
        sequence = 0L;
    }
} else {
    // 時間戳改變,重置序列號
    sequence = 0L;
}

lastTimestamp = ts;
/**
 * 阻塞到下一個毫秒
 *
 * @param lastTimestamp
 * @return
 */
private long tilNextMillis(long lastTimestamp) {
    long ts = System.currentTimeMillis();
    while (ts <= lastTimestamp) {
        ts = System.currentTimeMillis();
    }

    return ts;
}

生成最終的ID

return (ts - beginTs) << timestampLeftOffset | workerId << workerIdLeftOffset | sequence;

完整程式碼


最後貼出完整程式碼。

public class SnowflakeIdWorker {

    /**
     * 開始時間:2020-01-01 00:00:00
     */
    private final long beginTs = 1577808000000L;

    private final long workerIdBits = 10;

    /**
     * 2^10 - 1 = 1023
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    private final long sequenceBits = 12;

    /**
     * 2^12 - 1 = 4095
     */
    private final long maxSequence = -1L ^ (-1L << sequenceBits);

    /**
     * 時間戳左移22位
     */
    private final long timestampLeftOffset = workerIdBits + sequenceBits;

    /**
     * 業務ID左移12位
     */
    private final long workerIdLeftOffset = sequenceBits;

    /**
     * 合併了機器ID和資料標示ID,統稱業務ID,10位
     */
    private long workerId;

    /**
     * 毫秒內序列,12位,2^12 = 4096個數字
     */
    private long sequence = 0L;

    /**
     * 上一次生成的ID的時間戳,同一個worker中
     */
    private long lastTimestamp = -1L;

    public SnowflakeIdWorker(long workerId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("WorkerId必須大於或等於0且小於或等於%d", maxWorkerId));
        }

        this.workerId = workerId;
    }

    public synchronized long nextId() {
        long ts = System.currentTimeMillis();
        if (ts < lastTimestamp) {
            throw new RuntimeException(String.format("系統時鐘回退了%d毫秒", (lastTimestamp - ts)));
        }

        // 同一時間內,則計算序列號
        if (ts == lastTimestamp) {
            // 序列號溢位
            if (++sequence > maxSequence) {
                ts = tilNextMillis(lastTimestamp);
                sequence = 0L;
            }
        } else {
            // 時間戳改變,重置序列號
            sequence = 0L;
        }

        lastTimestamp = ts;

        // 0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000000 00 - 00000000 0000
        // 左移後,低位補0,進行按位或運算相當於二進位制拼接
        // 本來高位還有個0<<63,0與任何數字按位或都是本身,所以寫不寫效果一樣
        return (ts - beginTs) << timestampLeftOffset | workerId << workerIdLeftOffset | sequence;
    }

    /**
     * 阻塞到下一個毫秒
     *
     * @param lastTimestamp
     * @return
     */
    private long tilNextMillis(long lastTimestamp) {
        long ts = System.currentTimeMillis();
        while (ts <= lastTimestamp) {
            ts = System.currentTimeMillis();
        }

        return ts;
    }
}

補充


這裡面有大量的二進位制位運算,目的只有一個:快。

規則:1為真,0為否,其實就是同位之間的布林運算。

按位與:&

都為真就是真,其他都是否

1&1=1
1&0=0
0&1=0
0&0=0

按位或:|

只要有一個真就是真

1&1=1
1&0=1
0&1=1
0&0=0

異或:^

相同就是否,不同就是真

1&1=0
1&0=1
0&1=1
0&0=0

左移:<<

所有位向左移動多少位,低位補0,高位多出的直接刪掉

右移:>>

所有位向右移動多少位,低位多出的刪掉,高位是0補0,是1就補1

Java中的原碼、補碼和反碼

原碼

原碼就是十進位制數字的原始二進位制表示,對於整數而言,最高位為符號位,1表示負數,0表示正數。以32位int型的整數2及-2舉例:

2的原碼:00000000 00000000 00000000 00000010

-2的原碼:10000000 00000000 00000000 00000010

反碼

正數的反碼就是其原碼,負數的反碼除了最高位的符號位外,其他位取反(0改1,1改0)

2的反碼:00000000 00000000 00000000 00000010

-2的反碼:11111111 11111111 11111111 11111101

補碼

正數的補碼就是其原碼,負數的補碼是其反碼加1

2的補碼:00000000 00000000 00000000 00000010

-2的反碼:11111111 11111111 11111111 11111101

-2的補碼:11111111 11111111 11111111 11111110

&n