1. 程式人生 > >(飛歌工作筆記)分散式生成id--雪花演算法(snow)

(飛歌工作筆記)分散式生成id--雪花演算法(snow)

雪花演算法簡單描述: 
+ 最高位是符號位,始終為0,不可用。 
+ 41位的時間序列,精確到毫秒級,41位的長度可以使用69年。時間位還有一個很重要的作用是可以根據時間進行排序。 
+ 10位的機器標識,10位的長度最多支援部署1024個節點。 
+ 12位的計數序列號,序列號即一系列的自增id,可以支援同一節點同一毫秒生成多個ID序號,12位的計數序列號支援每個節點每毫秒產生4096個ID序號。

看的出來,這個演算法很簡潔也很簡單,但依舊是一個很好的ID生成策略。其中,10位器識別符號一般是5位IDC+5位machine編號,唯一確定一臺機器。

我不知道Twitter是如何確定10位機器標識和12位序列號的,不過,很明顯,這是可以改變的。

public class SnowFlakeGenerator {

    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 final static long START_STAMP = 1508143349995L;

    /**
     * 可分配的位數
     */
    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;

    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 = idcBitNum + sequenceBitNum;
        timestampBitLeftOffset = idcBitNum + machineBitNum + sequenceBitNum;

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

    /**
     * 產生下一個ID
     */
    public synchronized 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原始碼中的邏輯是迴圈,直到下一個毫秒
                lastStamp = 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;
    }
}