分布式全局ID生成器(雪花算法)


111111111111111111111111111111111111111111111111111111111111111
全局唯一ID生成服務 Twitter的分布式自增ID算法snowflake (Java版)

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

保證每個機器的workerId和datacenterId不同,比如你有3台服務器,那么這3台服務器的配置分別設置為0,1,2 就行了
需要指定數據中心標識ID和機器進程標識ID

問題:
系統時鍾回退

解決:
記錄 lasttime 並入庫,回退期間使用12位序列自增的方式生成id,序列滿后,lasttime 自增(計入內存並入庫)且序列歸0,開始下一輪的序列自增
數據庫壓力:
(因為單個應用是線程安全的同步操作不會出現並發)多應用同時出現時鍾回退且大量並發請求會有大量的入庫操作,
1,減少機器節點位數(workerId、datacenterId)增加序列節點到14位,序列可達到16000,減少lasttime的入庫頻率,
2,出現時鍾回退報警

 

10110001000111101000111001111011101111100 當前毫秒數 1970.1.1
1011110011100000010011011101101111100 減去20150101后的毫秒數
10111100111000000100110111011011111001111100001000000000000

2的62次方 4611686018427387904 100000000000000000000000000000000000000000000000000000000000000 63位
2的63次方-9223372036854775808 111111111111111111111111111111111111111111111111111111111111111 63位 減1 9223372036854775807

二進制11能標示4個數,但它是從0開始的,所以二進制11代表的是十進制的3,就是2的2次方減1
111111111111111111111111111111111111111111111111111111111111111,能表示9223372036854775808個數,對應十進制(減1)9223372036854775807,是Long的最大值
為什么2的63次方是負數:

最大值:Long.MAX_VALUE=9223372036854775807 (2的63次方-1)
最小值:Long.MIN_VALUE=-9223372036854775808 (-2的63次方)
數據用原碼表示:
0:00000000,00000000,00000000,00000000
8:00000000,00000000,00000000,00001000
Java沒有無符號整數,如果想表示負數呢?
可以讓第一個數字代表符號位,0是正數,1是負數,
表示的范圍是-2^16+1~-0和+0~2^16-1。
這導致了有+0和-0兩種0。
+8:00000000,00000000,00000000,00001000
-8:10000000,00000000,00000000,00001000
+0:00000000,00000000,00000000,00000000
-0:10000000,00000000,00000000,00000000

^ 異或運算 
& 按位與,轉化為二進制數  兩側的值都為真,結果才為真(二進制11111的十進制為31 11&31結果還未11,只要比31小,與的結果還為其本身)
  31&0 31&32 64 96 128 都為0
| 或運算,一個為真就為真(一個為1就為1),111(7)|110000(48)得110111(十進制55)
  如果第一個條件為true,會接着判斷第二個條件,和||不同
 

UUID 組成:
    UUID保證對在同一時空中的所有機器都是唯一的。通常平台會提供生成的API。按照開放軟件基金會(OSF)制定的標准計算,用到了以太網卡地址、納秒級時間、芯片ID碼和許多可能的數字
UUID由以下幾部分的組合:
(1)當前日期和時間,UUID的第一個部分與時間有關,如果你在生成一個UUID之后,過幾秒又生成一個UUID,則第一個部分不同,其余相同。
(2)時鍾序列。
(3)全局唯一的IEEE機器識別號,如果有網卡,從網卡MAC地址獲得,沒有網卡以其他方式獲得。
UUID的唯一缺陷在於生成的結果串會比較長。關於UUID這個標准使用最普遍的是微軟的GUID(Globals Unique Identifiers)。在ColdFusion中可以用CreateUUID()函數很簡單地生成UUID。

  1 package cn.com.gome.cashier.dynamic.test;
  2  
  3 /**
  4 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
  5 第一位為未使用,接下來的41位為毫秒級時間(41位的長度可以使用69年),然后是5位datacenterId和5位workerId(10位的長度最多支持部署1024個節點) ,最后12位是毫秒內的計數(12位的計數順序號支持每個節點每毫秒產生4096個ID序號)
  6 一共加起來剛好64位,為一個Long型。(轉換成字符串后長度最多19)
  7 https://www.cnblogs.com/relucent/p/4955340.html  http://www.wolfbe.com/detail/201611/381.html
  8  */
  9 public class SnowflakeIdWorker2 {
 10     //得到二進制樣例 10111100110111110011001010100001100111111100001000000000000
 11     /** 開始時間截 (2015-01-01) */
 12     private final long twepoch = 1420041600000L;
 13  
 14     //每一部分占用的位數
 15     /** 機器id所占的位數 */
 16     private final long workerIdBits = 5L;
 17     /** 數據標識id所占的位數 */
 18     private final long datacenterIdBits = 5L;
 19     /** 序列在id中占的位數 */
 20     private final long sequenceBits = 12L;
 21  
 22     //每一部分的最大值
 23     /** 支持的最大機器id,結果是31 (這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數) */
 24     private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
 25     /** 支持的最大數據標識id,結果是31 */
 26     private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 27     /** 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095) */
 28     private final long sequenceMask = -1L ^ (-1L << sequenceBits);
 29  
 30     //每一部分向左的位移
 31     /** 機器ID向左移12位 */
 32     private final long workerIdShift = sequenceBits;
 33     /** 數據標識id向左移17位(12+5) */
 34     private final long datacenterIdShift = sequenceBits + workerIdBits;
 35     /** 時間截向左移22位(5+5+12) */
 36     private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
 37  
 38     
 39  
 40     /** 工作機器ID(0~31) */
 41     private long workerId;
 42     /** 數據中心ID(0~31) */
 43     private long datacenterId;
 44     /** 毫秒內序列(0~4095) */
 45     private long sequence = 0L;
 46     /** 上次生成ID的時間截 */
 47     private long lastTimestamp = -1L;
 48  
 49     //==============================Constructors=====================================
 50     /**
 51      * 構造函數
 52      * @param workerId 工作ID (0~31)
 53      * @param datacenterId 數據中心ID (0~31)
 54      */
 55     public SnowflakeIdWorker2(long workerId, long datacenterId) {
 56         if (workerId > maxWorkerId || workerId < 0) {
 57             throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
 58         }
 59         if (datacenterId > maxDatacenterId || datacenterId < 0) {
 60             throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
 61         }
 62         this.workerId = workerId;
 63         this.datacenterId = datacenterId;
 64     }
 65  
 66     // ==============================Methods==========================================
 67     /**
 68      * 獲得下一個ID (該方法是線程安全的)
 69      * @return SnowflakeId
 70      */
 71     public synchronized long nextId() {
 72         long timestamp = timeGen();
 73  
 74         //如果當前時間小於上一次ID生成的時間戳,說明系統時鍾回退過這個時候應當拋出異常
 75         if (timestamp < lastTimestamp) {
 76             throw new RuntimeException(
 77                     String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
 78         }
 79  
 80         //如果是同一時間生成的,則進行毫秒內序列
 81         if (lastTimestamp == timestamp) {
 82             sequence = (sequence + 1) & sequenceMask;//相同毫秒內,序列號自增
 83             //毫秒內序列溢出 //同一毫秒的序列數已經達到最大
 84             if (sequence == 0) {
 85                 //阻塞到下一個毫秒,獲得新的時間戳
 86                 timestamp = tilNextMillis(lastTimestamp);
 87             }
 88         }
 89         //時間戳改變,毫秒內序列重置
 90         else {
 91             sequence = 0L;
 92         }
 93  
 94         //上次生成ID的時間截
 95         lastTimestamp = timestamp;
 96  
 97         System.out.println("timestamp: "+timestamp+","+(timestamp - twepoch));
 98         //移位並通過或運算拼到一起組成64位的ID,類似於相加的方式,當前時間減去2015-01-01,41bit的時間戳可以支持該算法使用年限:69年
 99         return ((timestamp - twepoch) << timestampLeftShift) //
100                 | (datacenterId << datacenterIdShift) //
101                 | (workerId << workerIdShift) //
102                 | sequence;
103     }
104  
105     /**
106      * 阻塞到下一個毫秒,直到獲得新的時間戳
107      * @param lastTimestamp 上次生成ID的時間截
108      * @return 當前時間戳
109      */
110     protected long tilNextMillis(long lastTimestamp) {
111         long timestamp = timeGen();
112         while (timestamp <= lastTimestamp) {
113             timestamp = timeGen();
114         }
115         return timestamp;
116     }
117  
118     /**
119      * 返回以毫秒為單位的當前時間
120      * @return 當前時間(毫秒)
121      */
122     protected long timeGen() {
123         return System.currentTimeMillis();
124     }
125  
126     //==============================Test=============================================
127     /** 測試 */
128     public static void main(String[] args) {
129         SnowflakeIdWorker2 idWorker = new SnowflakeIdWorker2(1, 31);
130         for (int i = 0; i < 10; i++) {
131             long id = idWorker.nextId();
132             System.out.println(Long.toBinaryString(id)+" "+id);
133         }
134     }
135 }

 


免責聲明!

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



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