1. 程式人生 > >SnowFlake全域性唯一ID及工具類

SnowFlake全域性唯一ID及工具類

正經學徒,佛系記錄,不搞事情

一、什麼是SnowFlake

twitter 用於生成id的演算法

真面目:64位的二進位制

snowflake

  • 1位,不用。二進位制中最高位為1的都是負數,但是我們生成的id一般都使用整數,所以這個最高位固定是0
  • 41位,用來記錄時間戳(毫秒)。 41位可以表示241−1個數字, 如果只用來表示正整數(計算機中正數包含0),可以表示的數值範圍是:0 至 241−1,減1是因為可表示的數值範圍是從0開始算的,而不是1。 也就是說41位可以表示241−1個毫秒的值,轉化成單位年則是(241−1)/(1000∗60∗60∗24∗365)=69年
  • 10位,用來記錄工作機器id。 可以部署在210=1024個節點,包括5位datacenterId和5位workerId 5位(bit)可以表示的最大正整數是25−1=31,即可以用0、1、2、3、....31這32個數字,來表示不同的datecenterId或workerId
  • 12位,序列號,用來記錄同毫秒內產生的不同id。 12位(bit)可以表示的最大正整數是212−1=4095,即可以用0、1、2、3、....4094這4095個數字,來表示同一機器同一時間截(毫秒)內產生的4095個ID序號 SnowFlake可以保證: 所有生成的id按時間趨勢遞增 整個分散式系統內不會產生重複id(因為有datacenterId和workerId來做區分)

二、為什麼用SnowFlake

對於mysql而言,InnoDB為聚集主鍵型別的引擎,資料會按照主鍵進行排序,由於UUID的無序性,InnoDB會產生巨大的IO壓力。InnoDB主鍵索引和資料儲存位置相關(簇類索引),uuid 主鍵可能會引起資料位置頻繁變動,嚴重影響效能,而雪花演算法的高位使用的是時間,因此保證了生成的ID的大小是遞增的,因此推薦使用雪花演算法。

mysql的首要推薦當然還是使用ID自增,但是這種做法不適合使用在分散式上,同時也有人覺得只用遞增會暴露業務資訊(比如通過ID判斷產品的銷量)

三、怎麼使用

注意:如下雪花演算法最終生成的字串長度是19位。使用時直接呼叫 getId 方法。

下面的工具類作用於單個服務節點,所以workerId和datacenterId都設為0。如果有多個機器節點則建議使用配置統一管理

/**
 * 全域性唯一id生成工具類
 */
public class SnowFlakeUtil {
    private long workerId;
    private long datacenterId;
    private long sequence = 0L;
    private long twepoch = 1288834974657L;                              //  Thu, 04 Nov 2010 01:42:54 GMT 標記時間 用來計算偏移量,距離當前時間不同,得到的資料的位數也不同
    private long workerIdBits = 5L;                                     //  物理節點ID長度
    private long datacenterIdBits = 5L;                                 //  資料中心ID長度
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);             //  最大支援機器節點數0~31,一共32個
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);     //  最大支援資料中心節點數0~31,一共32個
    private long sequenceBits = 12L;                                    //  序列號12位, 4095,同毫秒內生成不同id的最大個數
    private long workerIdShift = sequenceBits;                          //  機器節點左移12位
    private long datacenterIdShift = sequenceBits + workerIdBits;       //  資料中心節點左移17位
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; //  時間毫秒數左移22位
    private long sequenceMask = -1L ^ (-1L << sequenceBits);                          // 用於和當前時間戳做比較,以獲取最新時間
    private long lastTimestamp = -1L;

    //成員類,SnowFlakeUtil的例項物件的儲存域
    private static class IdGenHolder {
        private static final SnowFlakeUtil instance = new SnowFlakeUtil();
    }
    //外部呼叫獲取SnowFlakeUtil的例項物件,確保不可變
    public static SnowFlakeUtil get(){
        return IdGenHolder.instance;
    }
    //初始化構造,無參構造有參函式,預設節點都是0
    public SnowFlakeUtil() {
        this(0L, 0L);
    }
    //設定機器節點和資料中心節點數,都是 0-31
    public SnowFlakeUtil(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    //執行緒安全的id生成方法
    @SuppressWarnings("all")
    public synchronized long nextId() {
        //獲取當前毫秒數
        long timestamp = timeGen();
        //如果伺服器時間有問題(時鐘後退) 報錯。
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format(
                    "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        //如果上次生成時間和當前時間相同,在同一毫秒內
        if (lastTimestamp == timestamp) {
            //sequence自增,因為sequence只有12bit,所以和sequenceMask相與一下,去掉高位
            sequence = (sequence + 1) & sequenceMask;
            //判斷是否溢位,也就是每毫秒內超過4095,當為4096時,與sequenceMask相與,sequence就等於0
            if (sequence == 0) {
                //自旋等待到下一毫秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            //如果和上次生成時間不同,重置sequence,就是下一毫秒開始,sequence計數重新從0開始累加,每個毫秒時間內,都是從0開始計數,最大4095
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        // 最後按照規則拼出ID 64位
        // 000000000000000000000000000000000000000000  00000            00000       000000000000
        //1位固定整數   time                                       datacenterId   workerId    sequence
        return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift) | sequence;
    }

    //比較當前時間和過去時間,防止時鐘回退(機器問題),保證給的都是最新時間/最大時間
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    //獲取當前的時間戳(毫秒)
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * 獲取全域性唯一編碼
     */
    public static String getId(){
        Long id = SnowFlakeUtil.get().nextId();
        return id.toString();
    }
}