詳解分布式系統中的唯一id生成策略


系統唯一ID是我們在設計一個系統的時候常常會遇見的問題,也常常為這個問題而糾結。生成ID的方法有很多,適應不同的場景、需求以及性能要求。所以有些比較復雜的系統會有多個ID生成的策略。下面就介紹一些常見的ID生成策略。

平常應用中方式很多

1. 數據庫自增長序列或字段

2. UUID

3.uuid的變種

4.redis生成

5. Twitter的snowflake算法

snowflake是Twitter開源的分布式ID生成算法,結果是一個long型的ID。其核心思想是:使用41bit作為毫秒數,10bit作為機器的ID(5個bit是數據中心,5個bit的機器ID),12bit作為毫秒內的流水號(意味着每個節點在每毫秒可以產生 4096 個 ID),最后還有一個符號位,永遠是0。

本文主要是詳解snowflake算法的實現代碼解析

  1 /**
  2  * Twitter_Snowflake<br>
  3  * SnowFlake的結構如下(每部分用-分開):<br>
  4  * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
  5  * 1位標識,由於long基本類型在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0<br>
  6  * 41位時間截(毫秒級),注意,41位時間截不是存儲當前時間的時間截,而是存儲時間截的差值(當前時間截 - 開始時間截)
  7  * 得到的值),這里的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程序來指定的(如下下面程序IdWorker類的
  8  startTime屬性)。41位的時間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
  9  * 10位的數據機器位,可以部署在1024個節點,包括5位datacenterId和5位workerId<br>
 10  * 12位序列,毫秒內的計數,12位的計數順序號支持每個節點每毫秒(同一機器,同一時間截)產生4096個ID序號<br>
 11  * 加起來剛好64位,為一個Long型。<br>
 12  * SnowFlake的優點是,整體上按照時間自增排序,並且整個分布式系統內不會產生ID碰撞(由數據中心ID和機器ID作區分),並且效率較高,經測試,SnowFlake每秒能夠產生26萬ID左右。
 13  */
 14 public class SnowflakeIdWorker {
 15 
 16     // ==============================Fields===========================================
 17     /** 開始時間截 (2015-01-01) */
 18     private final long twepoch = 1420041600000L;
 19 
 20     /** 機器id所占的位數 */
 21     private final long workerIdBits = 5L;
 22 
 23     /** 數據標識id所占的位數 */
 24     private final long datacenterIdBits = 5L;
 25 
 26     /** 支持的最大機器id,結果是31 (這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數) */
 27     private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
 28 
 29     /** 支持的最大數據標識id,結果是31 */
 30     private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 31 
 32     /** 序列在id中占的位數 */
 33     private final long sequenceBits = 12L;
 34 
 35     /** 機器ID向左移12位 */
 36     private final long workerIdShift = sequenceBits;
 37 
 38     /** 數據標識id向左移17位(12+5) */
 39     private final long datacenterIdShift = sequenceBits + workerIdBits;
 40 
 41     /** 時間截向左移22位(5+5+12) */
 42     private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
 43 
 44     /** 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095) */
 45     private final long sequenceMask = -1L ^ (-1L << sequenceBits);
 46 
 47     /** 工作機器ID(0~31) */
 48     private long workerId;
 49 
 50     /** 數據中心ID(0~31) */
 51     private long datacenterId;
 52 
 53     /** 毫秒內序列(0~4095) */
 54     private long sequence = 0L;
 55 
 56     /** 上次生成ID的時間截 */
 57     private long lastTimestamp = -1L;
 58 
 59     //==============================Constructors=====================================
 60     /**
 61      * 構造函數
 62      * @param workerId 工作ID (0~31)
 63      * @param datacenterId 數據中心ID (0~31)
 64      */
 65     public SnowflakeIdWorker(long workerId, long datacenterId) {
 66         if (workerId > maxWorkerId || workerId < 0) {
 67             throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
 68         }
 69         if (datacenterId > maxDatacenterId || datacenterId < 0) {
 70             throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
 71         }
 72         this.workerId = workerId;
 73         this.datacenterId = datacenterId;
 74     }
 75 
 76     // ==============================Methods==========================================
 77     /**
 78      * 獲得下一個ID (該方法是線程安全的)
 79      * @return SnowflakeId
 80      */
 81     public synchronized long nextId() {
 82         long timestamp = timeGen();
 83 
 84         //如果當前時間小於上一次ID生成的時間戳,說明系統時鍾回退過這個時候應當拋出異常
 85         if (timestamp < lastTimestamp) {
 86             throw new RuntimeException(
 87                     String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
 88         }
 89 
 90         //如果是同一時間生成的,則進行毫秒內序列
 91         if (lastTimestamp == timestamp) {
 92             sequence = (sequence + 1) & sequenceMask;
 93             //毫秒內序列溢出
 94             if (sequence == 0) {
 95                 //阻塞到下一個毫秒,獲得新的時間戳
 96                 timestamp = tilNextMillis(lastTimestamp);
 97             }
 98         }
 99         //時間戳改變,毫秒內序列重置
100         else {
101             sequence = 0L;
102         }
103 
104         //上次生成ID的時間截
105         lastTimestamp = timestamp;
106 
107         //移位並通過或運算拼到一起組成64位的ID
108         return ((timestamp - twepoch) << timestampLeftShift) //
109                 | (datacenterId << datacenterIdShift) //
110                 | (workerId << workerIdShift) //
111                 | sequence;
112     }
113 
114     /**
115      * 阻塞到下一個毫秒,直到獲得新的時間戳
116      * @param lastTimestamp 上次生成ID的時間截
117      * @return 當前時間戳
118      */
119     protected long tilNextMillis(long lastTimestamp) {
120         long timestamp = timeGen();
121         while (timestamp <= lastTimestamp) {
122             timestamp = timeGen();
123         }
124         return timestamp;
125     }
126 
127     /**
128      * 返回以毫秒為單位的當前時間
129      * @return 當前時間(毫秒)
130      */
131     protected long timeGen() {
132         return System.currentTimeMillis();
133     }
134 
135     //==============================Test=============================================
136     /** 測試 */
137     public static void main(String[] args) {
138 //        System.out.println(Long.toBinaryString(5));
139         SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 1);
140         for (int i = 0; i < 1000; i++) {
141             long id = idWorker.nextId();
142             System.out.println(Long.toBinaryString(id));
143             System.out.println(id);
144         }
145     }
146 }

附帶有直接的測試方法,建議動手可以敲一下看一下其中的原理。

如有問題或者錯誤煩請指出!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM