天天看點

雪花算法(snowflake)實作分布式ID的生成

先分析一下:

雪花算法(snowflake)實作分布式ID的生成

雪花算法簡單描述: 

一、 1位辨別,由于long基本類型在Java中是帶符号的,最高位是符号位,正數是0,負數是1,是以id一般是正數,最高位是0,是符号位,始終為0,不可用。

二、41位的時間序列,精确到毫秒級,41位的長度可以使用69年。時間位還有一個很重要的作用是可以根據時間進行排序。注意,41位時間截不是存儲目前時間的時間截,而是存儲時間截的內插補點(目前時間截 - 開始時間截)

 得到的值),這裡的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程式來指定的(如下下面程式IdWorker類的startTime屬性)。41位的時間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69

三、10位的機器辨別,10位的長度最多支援部署1024個節點。包括5位datacenterId和5位workerId

四、12位的計數序列号,序列号即一系列的自增id,可以支援同一節點同一毫秒生成多個ID序号,12位的計數序列号支援每個節點每毫秒産生4096個ID序号。

其中,10位器辨別符一般是5位IDC+5位machine編号,唯一确定一台機器。

public class SnowFlakeGenerator {

    // 起始的時間戳    
    private final static long START_STAMP = 1566886337L;
/**
     * 可配置設定的位數
     */
    private final static int REMAIN_BIT_NUM = 22;

    /**
     * idc編号
     */
    private long idcId;

    /**
     * 機器編号
     */
    private long machineId;

    /**
     * 目前序列号
     */
    private long sequence = 0L;

    /**
     * 上次最新時間戳
     */
    private long lastStamp = -1L;
  
    //idc偏移量:一次計算出,避免重複計算    
    private int idcBitLeftOffset;
   
    //機器id偏移量:一次計算出,避免重複計算     
    private int machineBitLeftOffset;
   
    // 時間戳偏移量:一次計算出,避免重複計算    
    private int timestampBitLeftOffset;
   
    // 最大序列值:一次計算出,避免重複計算     
    private int maxSequenceValue;

    public static class Factory {
        //每一部分占用位數的預設值
        private final static int DEFAULT_MACHINE_BIT_NUM = 5;   //機器辨別占用的位數
        private final static int DEFAULT_IDC_BIT_NUM = 5;//資料中心占用的位數

        private int machineBitNum;
        private int idcBitNum;

        public Factory() {
            this.idcBitNum = DEFAULT_IDC_BIT_NUM;
            this.machineBitNum = DEFAULT_MACHINE_BIT_NUM;
        }

        public Factory(int machineBitNum, int idcBitNum) {
            this.idcBitNum = idcBitNum;
            this.machineBitNum = machineBitNum;
        }

        public SnowFlakeGenerator create(long idcId, long machineId) {
            return new SnowFlakeGenerator(this.idcBitNum, this.machineBitNum, idcId, machineId);
        }
    }

  
    private SnowFlakeGenerator(int idcBitNum, int machineBitNum, long idcId, long machineId) {
        int sequenceBitNum = REMAIN_BIT_NUM - idcBitNum - machineBitNum;

        if (idcBitNum <= 0 || machineBitNum <= 0 || sequenceBitNum <= 0) {
            throw new IllegalArgumentException("error bit number");
        }

        this.maxSequenceValue = ~(-1 << sequenceBitNum);

        machineBitLeftOffset = sequenceBitNum;  
        idcBitLeftOffset=machineBitNum+sequenceBitNum;
        timestampBitLeftOffset = idcBitNum + machineBitNum + sequenceBitNum;

        this.idcId = idcId;
        this.machineId = machineId;
    }

    
      // 産生下一個ID
        public long nextId() {
        long currentStamp = getTimeMill();
        if (currentStamp < lastStamp) {
            throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastStamp - currentStamp));
        }

        //新的毫秒,序列從0開始,否則序列自增
        if (currentStamp == lastStamp) {
            sequence = (sequence + 1) & this.maxSequenceValue;
            if (sequence == 0L) {
                //Twitter源代碼中的邏輯是循環,直到下一個毫秒
                currentStamp = tilNextMillis();
//                throw new IllegalStateException("sequence over flow");
            }
        } else {
            sequence = 0L;
        }

        lastStamp = currentStamp;

        return (currentStamp - START_STAMP) << timestampBitLeftOffset | idcId << idcBitLeftOffset | machineId << machineBitLeftOffset | sequence;
    }

    private long getTimeMill() {
        return System.currentTimeMillis();
    }

    private long tilNextMillis() {
        long timestamp = getTimeMill();
        while (timestamp <= lastStamp) {
            timestamp = getTimeMill();
        }
        return timestamp;
    }
}      

繼續閱讀