分佈式ID算法——snowflake(Java版)詳解

1、前言

snowflake算法是Twitter技術團隊在2010年開源的分佈式ID的生成算法,後續美團和百度都相應的根據該算法進行了改進,並且開源了其分佈式ID生成算法。本文將詳細介紹snowflake算法的數據結構以及其工作原理

2、數據結構

snowflake算法生成的分佈式ID是一個一個64bit大小的整數,其結構如下:
snowflake數據結構

  • 1位,佔位符不用,我們知道一般第一位表示正負數的符號,所以固定位爲0
  • 41位,用來記錄時間的時間戳(毫秒)
    41位可以表示的最大的數字爲2412^{41},其可以使用的最長的時間爲
    241÷(1000×60×60×24×356)=712^{41}\div(1000\times60\times60\times24\times356)=71
  • 10位,工作機器ID,可以部署在210=10242^{10}=1024個結點,其中5位爲workerId,5位爲datacenterId
  • 12位,序列號,用來記錄同毫秒內產生的ID,可以表示212=40962^{12}=4096個序列號

3、代碼詳解

Twitter的官方是用Scala寫的,在這裏我改成了Java版

public class IdWorker {

	//worker ID
    private long workerId;

    /**
     * 數據中心ID
     */
    private long datacenterId;

    /**
     * 系統起始時間
     * 2010/11/4 9:42:54
     */
    private long twepoch = 1288834974657L;

    /**
     * workerId位數
     */
    private long workerIdBits = 5L;

    /**
     * datacenter位數
     */
    private long datacenterIdBits = 5L;

    /**
     * 最大的workerId 0x1f = 31
     */
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /**
     * 最大的數據中心ID 0x1f = 31
     */
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /**
     * 序列位數
     */
    private long sequenceBits = 12L;


    /**
     * workerId位移 12位
     */
    private long workerIdShift = sequenceBits;

    /**
     * datacenter位移 17位
     */
    private long datacenterIdShift = sequenceBits + workerIdBits;

    /**
     * 時間位移 22位
     */
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /**
     * 序列掩碼,便於求餘
     */
    private long sequenceMask = -1L ^ (-1L << sequenceBits);


    private long lastTimestamp = -1L;

    private long sequence = 0L;


    public IdWorker(Long workerId, Long datacenterId) {

        if (workerId > maxWorkerId || workerId < 0) {
            System.out.println("worker Id can't be greater than %d or less than 0");
        }

        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            System.out.println("datacenter Id can't be greater than %d or less than 0");
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    private Long getWorkerId() {
        return workerId;
    }

    private Long getDataCenterId() {
        return datacenterId;
    }

    private Long getTimestamp() {
        return System.currentTimeMillis();
    }

    public synchronized Long getId() {
        //獲取當前時間戳
        Long timestamp = getTimeGen();
        if (timestamp < lastTimestamp) {
            System.out.println("error,clock is moving backwards.  Rejecting requests until ");
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                //阻塞到下一個毫秒
                timestamp = getNextMills(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;

        //整合時間戳-數據中心ID-workerID-序列號
        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

    /**
     * 阻塞到下一個毫秒
     * @param lastTimestamp
     * @return
     */
    protected Long getNextMills(Long lastTimestamp) {
        Long timestamp = getTimeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = getTimeGen();
        }
        return timestamp;
    }

    protected Long getTimeGen() {
        return System.currentTimeMillis();
    }
}

該算法的整體思路如下:

  1. 獲取ID的時候,先獲取當前的時間戳,和上一個時間戳比較,如果小,則產生了時鐘回撥,錯誤。
  2. 如果時間戳相等則序列號加1和序列號最大值求餘,當序列號爲0的時候則阻塞到下個毫秒
  3. 如果時間戳大於上一個時間戳,則序列號賦值爲0
  4. 通過位運算生成分佈式ID結果
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章