分佈式ID方案SnowFlake雪花算法分析


一、算法

SnowFlake算法生成的數據組成結構以下:java

snowflake.png

在java中用long類型標識,共64位(每部分用-分開): 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 0000000000 00算法

  • 1位標識,0表示正數。
  • 41位時間戳,當前時間的毫秒減去開始時間的毫秒數。可用 (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年。
  • 5位數據中心標識,可支持(1L << 5) = 32個數據中心。
  • 5位機器標識,每一個數據中心可支持(1L << 5) = 32個機器標識。
  • 12位序列號,每一個節點每一毫秒支持(1L << 12) = 4096個序列號。

二、Java版本實現

/**
 * 雪花算法<br>
 * 在java中用long類型標識,共64位(每部分用-分開):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 0000000000 00<br>
 * 1位標識,0表示正數。<br>
 * 41位時間戳,當前時間的毫秒減去開始時間的毫秒數。可用 (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年。<br>
 * 5位數據中心標識,可支持(1L << 5) = 32個數據中心。<br>
 * 5位機器標識,每一個數據中心可支持(1L << 5) = 32個機器標識。<br>
 * 12位序列號,每一個節點每一毫秒支持(1L << 12) = 4096個序列號。<br>
 */
public class SnowflakeIdWorker {
    /**
     * 機器標識
     */
    private long workerId;
    /**
     * 數據中心標識
     */
    private long dataCenterId;
    /**
     * 序列號
     */
    private long sequence;
    /**
     * 機器標識佔用5位
     */
    private long workerIdBits = 5L;
    /**
     * 數據中心標識佔用5位
     */
    private long dataCenterIdBits = 5L;
    /**
     * 12位序列號
     */
    private long sequenceBits = 12L;

    /**
     * 12位序列號支持的最大正整數
     * ....... 00001111 11111111
     * 2^12-1 = 4095
     */
    private long sequenceMask = ~(-1L << sequenceBits);

    /**
     * The Worker id shift.
     * 12位
     */
    private long workerIdShift = sequenceBits;
    /**
     * The Data center id shift.
     * 12 + 5 = 17位
     */
    private long dataCenterIdShift = sequenceBits + workerIdBits;
    /**
     * The Timestamp shift.
     * 12 + 5 + 5 = 22位
     */
    private long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits;
    /**
     * 開始時間戳毫秒
     */
    private long startEpoch = 29055616000L;
    /**
     * The Last timestamp.
     */
    private long lastTimestamp = -1L;

    public SnowflakeIdWorker(long workerId, long dataCenterId, long sequence) {
        // 檢查workerId是否正常
        /*
          機器標識最多支持的最大正整數
          -1的補碼:
          11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
          -1 左移 5 位,高位溢出,低位補0:
          11111111 11111111 11111111 11111111 11111111 11111111 11111111 11100000
          取反:
          00000000 00000000 00000000 00000000 00000000 00000000 00000000 00011111
          轉10進制:
          16 + 8 + 4 + 2 + 1 = 31
         */
        long maxWorkerId = ~(-1L << workerIdBits);
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("工做Id不能大於%d或小於0", maxWorkerId));
        }

        /*
          數據中心最多支持的最大正整數31
         */
        long maxDataCenterId = ~(-1L << dataCenterIdBits);
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("數據中心Id不能大於%d或小於0", maxDataCenterId));
        }

        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
        this.sequence = sequence;
    }

    private synchronized long nextId() {
        //獲取當前時間毫秒數
        long timestamp = timeGen();

        //若是當前時間毫秒數小於上一次的時間戳
        if (timestamp < lastTimestamp) {
            System.err.printf("時鐘發生回調,拒絕生成ID,直到: %d.", lastTimestamp);
            throw new RuntimeException(String.format("時鐘發生回調,  拒絕爲 %d 毫秒生成ID。",
                    lastTimestamp - timestamp));
        }

        //當前時間毫秒數與上次時間戳相同,增長序列號
        if (lastTimestamp == timestamp) {
            //假設sequence=4095
            //(4095 + 1) & 4095
            //4096:  ....... 00010000 00000000
            //4095:  ....... 00001111 11111111
            //       ....... 00000000 00000000
            //最終sequence爲0,即sequence發生溢出。
            sequence = (sequence + 1) & sequenceMask;
            //若是發生序列號爲0,即當前毫秒數的序列號已經溢出,則借用下一毫秒的時間戳
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            //當前毫秒數大於上次的時間戳,序列號爲0
            sequence = 0;
        }

        //更新
        lastTimestamp = timestamp;

        //生成ID算法,左移幾位,則後面加幾個0。
        //一、當前時間的毫秒數-開始時間的毫秒數,結果左移22位
        // 假設:timestamp - startEpoch = 1
        // 二進制:
        // 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
        // 左移22位:
        // 00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000
        //二、dataCenterId左移17位
        // 假設:dataCenterId = 1
        // 二進制:
        // 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
        // 左移17位:
        // 00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000

        //三、workerId左移12位
        // 假設:workerId = 1
        // 二進制:
        // 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
        // 左移12位:
        // 00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000
        //四、最後的全部結果按位`或`
        //假設:sequence = 1
        //00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000
        //00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000
        //00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000
        //00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
        //00000000 00000000 00000000 00000000 00000000 01000010 00010000 00000001
        //結果: 0 - 0000000 00000000 00000000 00000000 00000000 01 - 00001 - 00001 - 0000 00000001

        return ((timestamp - startEpoch) << timestampShift) |
                (dataCenterId << dataCenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

    /**
     * 獲取下一秒
     *
     * @param lastTimestamp the last timestamp
     * @return the long
     */
    private long tilNextMillis(long lastTimestamp) {
        //獲取當前毫秒數
        long timestamp = timeGen();
        //只要當前的毫秒數小於上次的時間戳,就一直循環,大於上次時間戳
        while (timestamp <= lastTimestamp) {
            //獲取當前毫秒數
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 獲取當前毫秒數
     *
     * @return the long
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }


    public static void main(String[] args) {
        SnowflakeIdWorker worker = new SnowflakeIdWorker(1, 1, 1);
        for (int i = 0; i < 10000; i++) {
            long id = worker.nextId();
            System.out.println(id);
            System.out.println(Long.toString(id).length());
            System.out.println(Long.toBinaryString(id));
            System.out.println(Long.toBinaryString(id).length());
        }
    }

}

三、難點

Tips: 左移幾位,則後面加幾個0。segmentfault

3.一、計算機器標識最多支持的最大正整數

private long workerIdBits = 5L;
long maxWorkerId = ~(-1L << workerIdBits);

計算過程:分佈式

  • -1的補碼:<br> 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
  • -1 左移 5 位,高位溢出,低位補0:<br> 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11100000
  • 取反:<br> 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00011111
  • 轉10進制:<br> 16 + 8 + 4 + 2 + 1 = 31

3.二、sequence溢出處理

sequence = (sequence + 1) & sequenceMask;

計算過程:<br> 假設sequence=4095:<br>post

  • (4095 + 1) & 4095
  • 4096: ....... 00010000 00000000
  • 4095: ....... 00001111 11111111
  • 按位與 ....... 00000000 00000000
  • 最終sequence爲0,即sequence發生溢出。

3.三、ID計算

((timestamp - startEpoch) << timestampShift) |
(dataCenterId << dataCenterIdShift) |
(workerId << workerIdShift) |
sequence

計算過程:this

  • 當前時間的毫秒數-開始時間的毫秒數,結果左移22位

假設:timestamp - startEpoch = 1<br> 二進制: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001<br> 左移22位: 00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000<br>code

  • dataCenterId左移17位

假設:dataCenterId = 1<br> 二進制: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001<br> 左移17位: 00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000<br>orm

  • workerId左移12位

假設:workerId = 1<br> 二進制: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001<br> 左移12位: 00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000<br>blog

  • 最後的全部結果按位

假設:sequence = 1<br> 00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000<br> 00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000<br> 00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000<br> 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001<br>

00000000 00000000 00000000 00000000 00000000 01000010 00010000 00000001<br>ip

  • 結果:

0 - 0000000 00000000 00000000 00000000 00000000 01 - 00001 - 00001 - 0000 00000001<br> 符合SnowFlake算法數據組成結構。

參考

理解分佈式id生成算法SnowFlake Twitter雪花算法SnowFlake算法的java實現


tencent.jpg

相關文章
相關標籤/搜索