Twitter的分布式自增ID演算法snowflake (Java版)

來源:互聯網
上載者:User

標籤:二進位   format   move   enter   安全執行緒   nal   接下來   runtime   cassandra   

概述

分布式系統中,有一些需要使用全域唯一ID的情境,這種時候為了防止ID衝突可以使用36位的UUID,但是UUID有一些缺點,首先他相對比較長,另外UUID一般是無序的。

有些時候我們希望能使用一種簡單一些的ID,並且希望ID能夠按照時間有序產生。

而twitter的snowflake解決了這種需求,最初Twitter把儲存系統從MySQL遷移到Cassandra,因為Cassandra沒有順序ID產生機制,所以開發了這樣一套全域唯一ID產生服務。

 

結構

snowflake的結構如下(每部分用-分開):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

第一位為未使用,接下來的41位為毫秒級時間(41位的長度可以使用69年),然後是5位datacenterId和5位workerId(10位的長度最多支援部署1024個節點) ,最後12位是毫秒內的計數(12位的計數順序號支援每個節點每毫秒產生4096個ID序號)

一共加起來剛好64位,為一個Long型。(轉換成字串後長度最多19)

snowflake產生的ID整體上按照時間自增排序,並且整個分布式系統內不會產生ID碰撞(由datacenter和workerId作區分),並且效率較高。經測試snowflake每秒能夠產生26萬個ID。

 

源碼

(JAVA版本的源碼)

/** * Twitter_Snowflake<br> * SnowFlake的結構如下(每部分用-分開):<br> * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br> * 1位標識,由於long基本類型在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0<br> * 41位時間截(毫秒級),注意,41位時間截不是儲存目前時間的時間截,而是儲存時間截的差值(目前時間截 - 開始時間截) * 得到的值),這裡的的開始時間截,一般是我們的id產生器開始使用的時間,由我們程式來指定的(如下下面程式IdWorker類的startTime屬性)。41位的時間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br> * 10位的資料機器位,可以部署在1024個節點,包括5位datacenterId和5位workerId<br> * 12位序列,毫秒內的計數,12位的計數順序號支援每個節點每毫秒(同一機器,同一時間截)產生4096個ID序號<br> * 加起來剛好64位,為一個Long型。<br> * SnowFlake的優點是,整體上按照時間自增排序,並且整個分布式系統內不會產生ID碰撞(由資料中心ID和機器ID作區分),並且效率較高,經測試,SnowFlake每秒能夠產生26萬ID左右。 */public class SnowflakeIdWorker {    // ==============================Fields===========================================    /** 開始時間截 (2015-01-01) */    private final long twepoch = 1420041600000L;    /** 機器id所佔的位元 */    private final long workerIdBits = 5L;    /** 資料標識id所佔的位元 */    private final long datacenterIdBits = 5L;    /** 支援的最大機器id,結果是31 (這個移位演算法可以很快的計算出幾位位元所能表示的最大十進位數) */    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);    /** 支援的最大資料標識id,結果是31 */    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);    /** 序列在id中占的位元 */    private final long sequenceBits = 12L;    /** 機器ID向左移12位 */    private final long workerIdShift = sequenceBits;    /** 資料標識id向左移17位(12+5) */    private final long datacenterIdShift = sequenceBits + workerIdBits;    /** 時間截向左移22位(5+5+12) */    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;    /** 產生序列的掩碼,這裡為4095 (0b111111111111=0xfff=4095) */    private final long sequenceMask = -1L ^ (-1L << sequenceBits);    /** 工作機器ID(0~31) */    private long workerId;    /** 資料中心ID(0~31) */    private long datacenterId;    /** 毫秒內序列(0~4095) */    private long sequence = 0L;    /** 上次產生ID的時間截 */    private long lastTimestamp = -1L;    //==============================Constructors=====================================    /**     * 建構函式     * @param workerId 工作ID (0~31)     * @param datacenterId 資料中心ID (0~31)     */    public SnowflakeIdWorker(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;    }    // ==============================Methods==========================================    /**     * 獲得下一個ID (該方法是安全執行緒的)     * @return SnowflakeId     */    public synchronized long nextId() {        long timestamp = timeGen();        //如果目前時間小於上一次ID產生的時間戳記,說明系統時鐘回退過這個時候應當拋出異常        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 + 1) & sequenceMask;            //毫秒內序列溢出            if (sequence == 0) {                //阻塞到下一個毫秒,獲得新的時間戳記                timestamp = tilNextMillis(lastTimestamp);            }        }        //時間戳記改變,毫秒內序列重設        else {            sequence = 0L;        }        //上次產生ID的時間截        lastTimestamp = timestamp;        //移位並通過或運算拼到一起組成64位的ID        return ((timestamp - twepoch) << timestampLeftShift) //                | (datacenterId << datacenterIdShift) //                | (workerId << workerIdShift) //                | sequence;    }    /**     * 阻塞到下一個毫秒,直到獲得新的時間戳記     * @param lastTimestamp 上次產生ID的時間截     * @return 目前時間戳     */    protected long tilNextMillis(long lastTimestamp) {        long timestamp = timeGen();        while (timestamp <= lastTimestamp) {            timestamp = timeGen();        }        return timestamp;    }    /**     * 返回以毫秒為單位的目前時間     * @return 目前時間(毫秒)     */    protected long timeGen() {        return System.currentTimeMillis();    }    //==============================Test=============================================    /** 測試 */    public static void main(String[] args) {        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);        for (int i = 0; i < 1000; i++) {            long id = idWorker.nextId();            System.out.println(Long.toBinaryString(id));            System.out.println(id);        }    }}

 

Twitter的分布式自增ID演算法snowflake (Java版)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.