精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

理解Snowflake算法的實現原理

開發 前端 算法
換言之, 大家目前使用的 Snowflake 算法原版或者改良版已經是十年前(當前是 2020 年)的產物,不得不說這個算法確實比較厲害 。

前提

Snowflake (雪花)是 Twitter 開源的高性能 ID 生成算法(服務)。

 

理解Snowflake算法的實現原理

上圖是 Snowflake 的 Github 倉庫, master 分支中的 REAEMDE 文件中提示:初始版本于 2010 年發布,基于 Apache Thrift ,早于 Finagle (這里的 Finagle 是 Twitter 上用于 RPC 服務的構建模塊)發布,而 Twitter 內部使用的 Snowflake 是一個完全重寫的程序,在很大程度上依靠 Twitter 上的現有基礎架構來運行。

而 2010 年發布的初版 Snowflake 源碼是使用 Scala 語言編寫的,歸檔于 scala_28 分支。換言之, 大家目前使用的 Snowflake 算法原版或者改良版已經是十年前(當前是 2020 年)的產物,不得不說這個算法確實比較厲害 。 scala_28 分支中有介紹該算法的動機和要求,這里簡單摘錄一下:

動機:

  • Cassandra 中沒有生成順序 ID 的工具, Twitter 由使用 MySQL 轉向使用 Cassandra 的時候需要一種新的方式來生成 ID (印證了架構不是設計出來,而是基于業務場景迭代出來)。

要求:

  • 高性能:每秒每個進程至少產生 10K 個 ID ,加上網絡延遲響應速度要在 2ms 內。
  • 順序性:具備按照時間的自增趨勢,可以直接排序。
  • 緊湊性:保持生成的 ID 的長度在 64 bit 或更短。
  • 高可用: ID 生成方案需要和存儲服務一樣高可用。
  • 下面就 Snowflake 的源碼分析一下他的實現原理。

Snowflake方案簡述

Snowflake 在初版設計方案是:

  • 時間: 41 bit 長度,使用毫秒級別精度,帶有一個自定義 epoch ,那么可以使用大概 69 年。
  • 可配置的機器 ID : 10 bit 長度,可以滿足 1024 個機器使用。
  • 序列號: 12 bit 長度,可以在 4096 個數字中隨機取值,從而避免單個機器在 1 ms 內生成重復的序列號。

 

理解Snowflake算法的實現原理

但是在實際源碼實現中, Snowflake 把 10 bit 的可配置的機器 ID 拆分為 5 bit 的 Worker ID (這個可以理解為原來的機器 ID )和 5 bit 的 Data Center ID (數據中心 ID ),詳情見 IdWorker.scala :

 

理解Snowflake算法的實現原理

也就是說,支持配置最多 32 個機器 ID 和最多 32 個數據中心 ID :

 

理解Snowflake算法的實現原理

由于算法是 Scala 語言編寫,是依賴于 JVM 的語言,返回的 ID 值為 Long 類型,也就是 64 bit 的整數,原來的算法生成序列中只使用了 63 bit 的長度,要返回的是無符號數,所以在高位補一個 0 (占用 1 bit ),那么加起來整個 ID 的長度就是 64 bit :

 

理解Snowflake算法的實現原理

其中:

  • 41 bit 毫秒級別時間戳的取值范圍是: [0, 2^41 - 1] => 0 ~ 2199023255551 ,一共 2199023255552 個數字。
  • 5 bit 機器 ID 的取值范圍是: [0, 2^5 - 1] => 0 ~ 31 ,一共 32 個數字。
  • 5 bit 數據中心 ID 的取值范圍是: [0, 2^5 - 1] => 0 ~ 31 ,一共 32 個數字。
  • 12 bit 序列號的取值范圍是: [0, 2^12 - 1] => 0 ~ 4095 ,一共 4096 個數字。

那么理論上可以生成 2199023255552 * 32 * 32 * 4096 個完全不同的 ID 值。

Snowflake 算法還有一個明顯的特征: 依賴于系統時鐘 。 41 bit 長度毫秒級別的時間來源于系統時間戳,所以必須保證系統時間是向前遞進,不能發生 時鐘回撥 (通說來說就是不能在同一個時刻產生多個相同的時間戳或者產生了過去的時間戳)。一旦發生時鐘回撥, Snowflake 會拒絕生成下一個 ID 。

位運算知識補充

Snowflake 算法中使用了大量的位運算。由于整數的補碼才是在計算機中的存儲形式, Java 或者 Scala 中的整型都使用補碼表示,這里稍微提一下原碼和補碼的知識。

  • 原碼用于閱讀,補碼用于計算。
  • 正數的補碼與其原碼相同。
  • 負數的補碼是除最高位其他所有位取反,然后加 1 (反碼加 1 ),而負數的補碼還原為原碼也是使用這個方式。
  • +0 的原碼是 0000 0000 ,而 -0 的原碼是 1000 0000 ,補碼只有一個 0 值,用 0000 0000 表示,這一點很重要,補碼的 0 沒有二義性。

簡單來看就是這樣:

  1. * [+ 11] 原碼 = [0000 1011] 補碼 = [0000 1011] 
  2. * [- 11] 原碼 = [1000 1011] 補碼 = [1111 0101] 
  3.  
  4. * [- 11]的補碼計算過程:  
  5.         原碼                  1000 1011 
  6.         除了最高位其他位取反   1111 0100 
  7.         加1                   1111 0101  (補碼) 

使用原碼、反碼在計算的時候得到的不一定是準確的值,而使用補碼的時候計算結果才是正確的,記住這個結論即可,這里不在舉例。由于 Snowflake 的 ID 生成方案中,除了最高位,其他四個部分都是無符號整數,所以四個部分的整數 使用補碼進行位運算的效率會比較高,也只有這樣才能滿足Snowflake高性能設計的初衷 。 Snowflake 算法中使用了幾種位運算:異或( ^ )、按位與( & )、按位或( | )和帶符號左移( << )。

異或

異或的運算規則是: 0^0=0 0^1=1 1^0=1 1^1=0 ,也就是位不同則結果為1,位相同則結果為0。主要作用是:

  • 特定位翻轉,也就是一個數和 N 個位都為 1 的數進行異或操作,這對應的 N 個位都會翻轉,例如 0100 & 1111 ,結果就是 1011 。
  • 與 0 項異或,則結果和原來的值一致。
  • 兩數的值交互: a=a^b b=b^a a=a^b ,這三個操作完成之后, a 和 b 的值完成交換。

這里推演一下最后一條:

  1. * [+ 11] 原碼 = [0000 1011] 補碼 = [0000 1011] a 
  2. * [- 11] 原碼 = [1000 1011] 補碼 = [1111 0101] b 
  3.  
  4. a=a^b          0000 1011 
  5.                1111 0101 
  6.                ---------^ 
  7.                1111 1110 
  8. b=b^a          1111 0101 
  9.                ---------^ 
  10.                0000 1011  (十進制數:11) b 
  11. a=a^b          1111 1110 
  12.                ---------^ 
  13.                1111 0101  (十進制數:-11) a 

按位與

按位與的運算規則是: 0^0=0 0^1=0 1^0=0 1^1=1 ,只有對應的位都為1的時候計算結果才是1,其他情況的計算結果都是0。主要作用是:

  • 清零,如果想把一個數清零,那么和所有位為 0 的數進行按位與即可。
  • 取一個數中的指定位,例如要取 X 中的低 4 位,只需要和 zzzz...1111 進行按位與即可,例如取 1111 0110 的低 4 位,則 11110110 & 00001111 即可得到 00000110 。

按位或

按位與的運算規則是: 0^0=0 0^1=1 1^0=1 1^1=1 ,只要有其中一個位存在1則計算結果是1,只有兩個位同時為0的情況下計算結果才是0。主要作用是:

  • 對一個數的部分位賦值為 1 ,只需要和對應位全為 0 的數做按位或操作就行,例如 1011 0000 如果低 4 位想全部賦值為 1 ,那么 10110000 | 00001111 即可得到 1011 1111 。

帶符號左移

帶符號左移的運算符是 << ,一般格式是: M << n 。作用如下:

  • M 的二進制數(補碼)向左移動 n 位。
  • 左邊(高位)移出部分直接舍棄,右邊(低位)移入部分全部補 0 。
  • 移位結果:相當于 M 的值乘以 2 的 n 次方,并且0、正、負數通用。
  • 移動的位數超過了該類型的最大位數,那么編譯器會對移動的位數取模,例如 int 移位 33 位,實際上只移動了 33 % 2 = 1 位。

推演過程如下(假設 n = 2 ):

  1. * [+ 11] 原碼 = [0000 1011] 補碼 = [0000 1011] 
  2. * [- 11] 原碼 = [1000 1011] 補碼 = [1111 0101] 
  3.  
  4. * [+ 11 << 2]的計算過程 
  5.       補碼          0000 1011 
  6.       左移2位     0000 1011   
  7.       舍高補低      0010 1100 
  8.       十進制數    2^2 + 2^3 + 2^5 = 44 
  9.  
  10. * [- 11 << 2]的計算過程 
  11.       補碼          1111 0101 
  12.       左移2位     1111 0101   
  13.       舍高補低      1101 0100  
  14.       原碼          1010 1100 (補碼除最高位其他所有位取反再加1) 
  15.       十進制數    - (2^2 + 2^3 + 2^5) = -44 

可以寫個 main 方法驗證一下:

  1. public static void main(String[] args) { 
  2.       System.out.println(-11 << 2); // -44 
  3.       System.out.println(11 << 2);  // 44 

組合技巧

利用上面提到的三個位運算符,相互組合可以實現一些高效的計算方案。

計算n個bit能表示的最大數值:

Snowflake 算法中有這樣的代碼:

  1. // 機器ID的位長度 
  2. private val workerIdBits = 5L; 
  3. // 最大機器ID -> 31 
  4. private val maxWorkerId = -1L ^ (-1L << workerIdBits); 

這里的算子是 -1L ^ (-1L << 5L) ,整理運算符的順序,再使用 64 bit 的二進制數推演計算過程如下:

  1. * [-1] 的補碼         11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 
  2.   左移5位             11111111 11111111 11111111 11111111 11111111 11111111 11111111 11100000 
  3.   [-1] 的補碼         11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 
  4.   異或                ----------------------------------------------------------------------- ^  
  5.   結果的補碼          00000000 00000000 00000000 00000000 00000000 00000000 00000000 00011111  (十進制數 2^0 + 2^1 + 2^2 + 2^3 + 2^4 = 31) 

這樣就能計算出 5 bit 能表示的最大數值 n , n 為整數并且 0 <= n <= 31 ,即 0、1、2、3...31 。 Worker ID 和 Data Center ID 部分的最大值就是使用這種組合運算得出的。

用固定位的最大值作為Mask避免溢出:

Snowflake 算法中有這樣的代碼:

  1. var sequence = 0L 
  2. ...... 
  3. private val sequenceBits = 12L 
  4. // 這里得到的是sequence的最大值4095 
  5. private val sequenceMask = -1L ^ (-1L << sequenceBits) 
  6. ...... 
  7. sequence = (sequence + 1) & sequenceMask 

最后這個算子其實就是 sequence = (sequence + 1) & 4095 ,假設 sequence 當前值為 4095 ,推演一下計算過程:

  1. * [4095] 的補碼                 00000000 00000000 00000000 00000000 00000000 00000000 00000111 11111111 
  2.   [sequence + 1] 的補碼         00000000 00000000 00000000 00000000 00000000 00000000 00001000 00000000 
  3.   按位與                        ----------------------------------------------------------------------- & 
  4.   計算結果                      00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000  (十進制數:0) 

可以編寫一個 main 方法驗證一下:

  1. public static void main(String[] args) { 
  2.     int mask = 4095; 
  3.     System.out.println(0 & mask); // 0 
  4.     System.out.println(1 & mask); // 1 
  5.     System.out.println(2 & mask); // 2 
  6.     System.out.println(4095 & mask); // 4095 
  7.     System.out.println(4096 & mask); // 0 
  8.     System.out.println(4097 & mask); // 1 

也就是 x = (x + 1) & (-1L ^ (-1L << N)) 能保證最終得到的 x 值不會超過 N ,這是利用了按位與中的"取指定位"的特性。

Snowflake算法實現源碼分析

Snowflake 雖然用 Scala 語言編寫,語法其實和 Java 差不多,當成 Java 代碼這樣閱讀就行,下面閱讀代碼的時候會跳過一些日志記錄和度量統計的邏輯。先看 IdWorker.scala 的屬性值:

  1. // 定義基準紀元值,這個值是北京時間2010-11-04 09:42:54,估計就是2010年初版提交代碼時候定義的一個時間戳 
  2. val twepoch = 1288834974657L 
  3.  
  4. // 初始化序列號為0 
  5. var sequence = 0L //TODO after 2.8 make this a constructor param with a default of 0 
  6.  
  7. // 機器ID的最大位長度為5 
  8. private val workerIdBits = 5L 
  9.  
  10. // 數據中心ID的最大位長度為5 
  11. private val datacenterIdBits = 5L 
  12.  
  13. // 最大的機器ID值,十進制數為為31 
  14. private val maxWorkerId = -1L ^ (-1L << workerIdBits) 
  15.  
  16. // 最大的數據中心ID值,十進制數為為31 
  17. private val maxDatacenterId = -1L ^ (-1L << datacenterIdBits) 
  18.  
  19. // 序列號的最大位長度為12 
  20. private val sequenceBits = 12L 
  21.  
  22. // 機器ID需要左移的位數12 
  23. private val workerIdShift = sequenceBits 
  24.  
  25. // 數據中心ID需要左移的位數 = 12 + 5 
  26. private val datacenterIdShift = sequenceBits + workerIdBits 
  27.  
  28. // 時間戳需要左移的位數 = 12 + 5 + 5 
  29. private val timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits 
  30.  
  31. // 序列號的掩碼,十進制數為4095 
  32. private val sequenceMask = -1L ^ (-1L << sequenceBits) 
  33.  
  34. // 初始化上一個時間戳快照值為-1 
  35. private var lastTimestamp = -1L 
  36.  
  37. // 下面的代碼塊為參數校驗和初始化日志打印,這里不做分析 
  38. if (workerId > maxWorkerId || workerId < 0) { 
  39. exceptionCounter.incr(1) 
  40. throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0".format(maxWorkerId)) 
  41.  
  42. if (datacenterId > maxDatacenterId || datacenterId < 0) { 
  43. exceptionCounter.incr(1) 
  44. throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0".format(maxDatacenterId)) 
  45.  
  46. log.info("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d"
  47. timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId) 

 

理解Snowflake算法的實現原理

接著看算法的核心代碼邏輯:

  1. // 同步方法,其實就是protected synchronized long nextId(){ ...... } 
  2. protected[snowflake] def nextId(): Long = synchronized { 
  3.     // 獲取系統時間戳(毫秒) 
  4.     var timestamp = timeGen() 
  5.     // 高并發場景,同一毫秒內生成多個ID 
  6.     if (lastTimestamp == timestamp) { 
  7.         // 確保sequence + 1之后不會溢出,最大值為4095,其實也就是保證1毫秒內最多生成4096個ID值 
  8.         sequence = (sequence + 1) & sequenceMask 
  9.         // 如果sequence溢出則變為0,說明1毫秒內并發生成的ID數量超過了4096個,這個時候同1毫秒的第4097個生成的ID必須等待下一毫秒 
  10.         if (sequence == 0) { 
  11.             // 死循環等待下一個毫秒值,直到比lastTimestamp大 
  12.             timestamp = tilNextMillis(lastTimestamp) 
  13.         } 
  14.     } else { 
  15.         // 低并發場景,不同毫秒中生成ID 
  16.         // 不同毫秒的情況下,由于外層方法保證了timestamp大于或者小于lastTimestamp,而小于的情況是發生了時鐘回撥,下面會拋出異常,所以不用考慮 
  17.         // 也就是只需要考慮一種情況:timestamp > lastTimestamp,也就是當前生成的ID所在的毫秒數比上一個ID大 
  18.         // 所以如果時間戳部分增大,可以確定整數值一定變大,所以序列號其實可以不用計算,這里直接賦值為0 
  19.         sequence = 0 
  20.     } 
  21.     // 獲取到的時間戳比上一個保存的時間戳小,說明時鐘回撥,這種情況下直接拋出異常,拒絕生成ID 
  22.     // 個人認為,這個方法應該可以提前到var timestamp = timeGen()這段代碼之后 
  23.     if (timestamp < lastTimestamp) { 
  24.       exceptionCounter.incr(1) 
  25.       log.error("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp); 
  26.       throw new InvalidSystemClock("Clock moved backwards.  Refusing to generate id for %d milliseconds".format(lastTimestamp - timestamp)); 
  27.     } 
  28.     // lastTimestamp保存當前時間戳,作為方法下次被調用的上一個時間戳的快照 
  29.     lastTimestamp = timestamp 
  30.     // 度量統計,生成的ID計數器加1 
  31.     genCounter.incr() 
  32.     // X = (系統時間戳 - 自定義的紀元值) 然后左移22位 
  33.     // Y = (數據中心ID左移17位) 
  34.     // Z = (機器ID左移12位) 
  35.     // 最后ID = X | Y | Z | 計算出來的序列號sequence 
  36.     ((timestamp - twepoch) << timestampLeftShift) | 
  37.       (datacenterId << datacenterIdShift) | 
  38.       (workerId << workerIdShift) |  
  39.       sequence 
  40.  
  41. // 輔助方法:獲取系統當前的時間戳(毫秒) 
  42. protected def timeGen(): Long = System.currentTimeMillis() 
  43.  
  44. // 輔助方法:獲取系統當前的時間戳(毫秒),用死循環保證比傳入的lastTimestamp大,也就是獲取下一個比lastTimestamp大的毫秒數 
  45. protected def tilNextMillis(lastTimestamp: Long): Long = { 
  46.     var timestamp = timeGen() 
  47.     while (timestamp <= lastTimestamp) { 
  48.       timestamp = timeGen() 
  49.     } 
  50.     timestamp 

最后一段邏輯的位操作比較多,但是如果熟練使用位運算操作符,其實邏輯并不復雜,這里可以畫個圖推演一下:

 

理解Snowflake算法的實現原理

四個部分的整數完成左移之后,由于空缺的低位都會補充了 0 ,基于按位或的特性,所有低位只要存在 1 ,那么對應的位就會填充為 1 ,由于四個部分的位不會越界分配,所以這里的本質就是: 四個部分左移完畢后最終的數字進行加法計算 。

Snowflake算法改良

Snowflake 算法有幾個比較大的問題:

  • 低并發場景會產生連續偶數,原因是低并發場景系統時鐘總是走到下一個毫秒值,導致序列號重置為 0 。
  • 依賴系統時鐘,時鐘回撥會拒絕生成新的 ID (直接拋出異常)。
  • Woker ID 和 Data Center ID 的管理比較麻煩,特別是同一個服務的不同集群節點需要保證每個節點的 Woker ID 和 Data Center ID 組合唯一。

這三個問題美團開源的 Leaf 提供了解決思路,下圖截取自 com.sankuai.inf.leaf.snowflake.SnowflakeIDGenImpl :

 

理解Snowflake算法的實現原理

對應的解決思路是(不進行深入的源碼分析,有興趣可以閱讀以下 Leaf 的源碼):

  • 序列號生成添加隨機源,會稍微減少同一個毫秒內能產生的最大 ID 數量。
  • 時鐘回撥則進行一定期限的等待。
  • 使用 Zookeeper 緩存和管理 Woker ID 和 Data Center ID 。

Woker ID 和 Data Center ID 的配置是極其重要的,對于同一個服務(例如支付服務)集群的多個節點,必須配置不同的機器 ID 和數據中心 ID 或者同樣的數據中心 ID 和不同的機器 ID ( 簡單說就是確保 Woker ID 和 Data Center ID 的組合全局唯一 ),否則在高并發的場景下,在系統時鐘一致的情況下,很容易在多個節點產生相同的 ID 值,所以一般的部署架構如下:

 

理解Snowflake算法的實現原理

管理這兩個 ID 的方式有很多種,或者像 Leaf 這樣的開源框架引入分布式緩存進行管理,再如筆者所在的創業小團隊生產服務比較少,直接把 Woker ID 和 Data Center ID 硬編碼在服務啟動腳本中,然后把所有服務使用的 Woker ID 和 Data Center ID 統一登記在團隊內部知識庫中。

自實現簡化版Snowflake

如果完全不考慮性能的話,也不考慮時鐘回撥、序列號生成等等問題,其實可以把 Snowflake 的位運算和異常處理部分全部去掉,使用 Long.toBinaryString() 方法結合字符串按照 Snowflake 算法思路拼接出 64 bit 的二進制數,再通過 Long.parseLong() 方法轉化為 Long 類型。編寫一個 main 方法如下:

  1. public class Main { 
  2.  
  3.     private static final String HIGH = "0"
  4.  
  5.     /** 
  6.      * 2020-08-01 00:00:00 
  7.      */ 
  8.     private static final long EPOCH = 1596211200000L; 
  9.  
  10.     public static void main(String[] args) { 
  11.         long workerId = 1L; 
  12.         long dataCenterId = 1L; 
  13.         long seq = 4095; 
  14.         String timestampString = leftPadding(Long.toBinaryString(System.currentTimeMillis() - EPOCH), 41); 
  15.         String workerIdString = leftPadding(Long.toBinaryString(workerId), 5); 
  16.         String dataCenterIdString = leftPadding(Long.toBinaryString(dataCenterId), 5); 
  17.         String seqString = leftPadding(Long.toBinaryString(seq), 12); 
  18.         String value = HIGH + timestampString + workerIdString + dataCenterIdString + seqString; 
  19.         long num = Long.parseLong(value, 2); 
  20.         System.out.println(num);   // 某個時刻輸出為3125927076831231 
  21.     } 
  22.  
  23.     private static String leftPadding(String value, int maxLength) { 
  24.         int diff = maxLength - value.length(); 
  25.         StringBuilder builder = new StringBuilder(); 
  26.         for (int i = 0; i < diff; i++) { 
  27.             builder.append("0"); 
  28.         } 
  29.         builder.append(value); 
  30.         return builder.toString(); 
  31.     } 

然后把代碼規范一下,編寫出一個簡版 Snowflake 算法實現的工程化代碼:

  1. // 主鍵生成器接口 
  2. public interface PrimaryKeyGenerator { 
  3.  
  4.     long generate(); 
  5.  
  6. // 簡易Snowflake實現 
  7. public class SimpleSnowflake implements PrimaryKeyGenerator { 
  8.  
  9.     private static final String HIGH = "0"
  10.     private static final long MAX_WORKER_ID = 31; 
  11.     private static final long MIN_WORKER_ID = 0; 
  12.  
  13.     private static final long MAX_DC_ID = 31; 
  14.     private static final long MIN_DC_ID = 0; 
  15.  
  16.     private static final long MAX_SEQUENCE = 4095; 
  17.  
  18.     /** 
  19.      * 機器ID 
  20.      */ 
  21.     private final long workerId; 
  22.  
  23.     /** 
  24.      * 數據中心ID 
  25.      */ 
  26.     private final long dataCenterId; 
  27.  
  28.     /** 
  29.      * 基準紀元值 
  30.      */ 
  31.     private final long epoch; 
  32.  
  33.     private long sequence = 0L; 
  34.     private long lastTimestamp = -1L; 
  35.  
  36.     public SimpleSnowflake(long workerId, long dataCenterId, long epoch) { 
  37.         this.workerId = workerId; 
  38.         this.dataCenterId = dataCenterId; 
  39.         this.epoch = epoch; 
  40.         checkArgs(); 
  41.     } 
  42.  
  43.     private void checkArgs() { 
  44.         if (!(MIN_WORKER_ID <= workerId && workerId <= MAX_WORKER_ID)) { 
  45.             throw new IllegalArgumentException("Worker id must be in [0,31]"); 
  46.         } 
  47.         if (!(MIN_DC_ID <= dataCenterId && dataCenterId <= MAX_DC_ID)) { 
  48.             throw new IllegalArgumentException("Data center id must be in [0,31]"); 
  49.         } 
  50.     } 
  51.  
  52.     @Override 
  53.     public synchronized long generate() { 
  54.         long timestamp = System.currentTimeMillis(); 
  55.         // 時鐘回撥 
  56.         if (timestamp < lastTimestamp) { 
  57.             throw new IllegalStateException("Clock moved backwards"); 
  58.         } 
  59.         // 同一毫秒內并發 
  60.         if (lastTimestamp == timestamp) { 
  61.             sequence = sequence + 1; 
  62.             if (sequence == MAX_SEQUENCE) { 
  63.                 timestamp = untilNextMillis(lastTimestamp); 
  64.                 sequence = 0L; 
  65.             } 
  66.         } else { 
  67.             // 下一毫秒重置sequence為0 
  68.             sequence = 0L; 
  69.         } 
  70.         lastTimestamp = timestamp
  71.         // 41位時間戳字符串,不夠位數左邊補"0" 
  72.         String timestampString = leftPadding(Long.toBinaryString(timestamp - epoch), 41); 
  73.         // 5位機器ID字符串,不夠位數左邊補"0" 
  74.         String workerIdString = leftPadding(Long.toBinaryString(workerId), 5); 
  75.         // 5位數據中心ID字符串,不夠位數左邊補"0" 
  76.         String dataCenterIdString = leftPadding(Long.toBinaryString(dataCenterId), 5); 
  77.         // 12位序列號字符串,不夠位數左邊補"0" 
  78.         String seqString = leftPadding(Long.toBinaryString(sequence), 12); 
  79.         String value = HIGH + timestampString + workerIdString + dataCenterIdString + seqString; 
  80.         return Long.parseLong(value, 2); 
  81.     } 
  82.  
  83.     private long untilNextMillis(long lastTimestamp) { 
  84.         long timestamp
  85.         do { 
  86.             timestamp = System.currentTimeMillis(); 
  87.         } while (timestamp <= lastTimestamp); 
  88.         return timestamp
  89.     } 
  90.  
  91.     private static String leftPadding(String value, int maxLength) { 
  92.         int diff = maxLength - value.length(); 
  93.         StringBuilder builder = new StringBuilder(); 
  94.         for (int i = 0; i < diff; i++) { 
  95.             builder.append("0"); 
  96.         } 
  97.         builder.append(value); 
  98.         return builder.toString(); 
  99.     } 
  100.  
  101.     public static void main(String[] args) { 
  102.         long epoch = LocalDateTime.of(1970, 1, 1, 0, 0, 0, 0) 
  103.                 .toInstant(ZoneOffset.of("+8")).toEpochMilli(); 
  104.         PrimaryKeyGenerator generator = new SimpleSnowflake(1L, 1L, epoch); 
  105.         for (int i = 0; i < 5; i++) { 
  106.             System.out.println(String.format("第%s個生成的ID: %d", i + 1, generator.generate())); 
  107.         } 
  108.     } 
  109.  
  110. // 某個時刻輸出如下 
  111. 第1個生成的ID: 6698247966366502912 
  112. 第2個生成的ID: 6698248027448152064 
  113. 第3個生成的ID: 6698248032162549760 
  114. 第4個生成的ID: 6698248033076908032 
  115. 第5個生成的ID: 6698248033827688448 

通過字符串拼接的寫法雖然運行效率低,但是可讀性會比較高,工程化處理后的代碼可以在實例化時候直接指定 Worker ID 和 Data Center ID 等值,并且這個簡易的 Snowflake 實現沒有第三方庫依賴,拷貝下來可以直接運行。上面的方法使用字符串拼接看起來比較低端,其實最后那部分的按位或, 可以完全轉化為加法 :

  1. public class Main { 
  2.      
  3.     /** 
  4.      * 2020-08-01 00:00:00 
  5.      */ 
  6.     private static final long EPOCH = 1596211200000L; 
  7.  
  8.     public static void main(String[] args) { 
  9.         long workerId = 1L; 
  10.         long dataCenterId = 1L; 
  11.         long seq = 4095; 
  12.         long timestampDiff = System.currentTimeMillis() - EPOCH; 
  13.         long num = (long) (timestampDiff * Math.pow(2, 22)) + (long) (dataCenterId * Math.pow(2, 17)) + (long) (workerId * Math.pow(2, 12)) + seq; 
  14.         System.out.println(num);   // 某個時刻輸出為3248473482862591 
  15.     } 

這樣看起來整個算法都變得簡單,不過這里涉及到指數運算和加法運算,效率會比較低。

小結

Snowflake 算法是以高性能為核心目標的算法,基于這一點目的巧妙地大量使用位運算,這篇文章已經把 Snowflake 中應用到的位運算和具體源碼實現徹底分析清楚。最后,基于 Twitter 官方的 Snowflake 算法源碼,修訂出了一版 Java 實現版本,并且應用前面提到的改良方式,修復了低并發場景下只產生偶數的問題, 并且已經應用于生產環境一段時間 ,代碼倉庫如下(代碼沒有任何第三方庫依賴,拷貝出來就直接可用):

Github : https://github.com/zjcscut/framework-mesh/tree/master/java-snowflake

 

責任編輯:未麗燕 來源: 今日頭條
相關推薦

2022-07-06 08:30:36

vuereactvdom

2021-10-21 10:02:37

Java開發代碼

2021-10-10 13:31:14

Java負載均衡算法

2021-03-06 14:41:07

布隆過濾器算法

2022-10-24 08:08:27

閉包編譯器

2017-03-02 10:49:37

推薦算法原理實現

2024-10-16 09:57:52

空結構體map屬性

2022-07-27 22:59:53

Node.jsNest

2019-08-20 14:01:22

HTTPSSSL協議

2023-09-12 08:00:00

大數據數據管理Snowflake

2018-07-27 08:39:44

負載均衡算法實現

2021-05-21 05:22:52

腳手架工具項目

2025-01-16 07:10:00

2020-02-19 19:18:02

緩存查詢速度淘汰算法

2009-04-02 10:23:13

實現JoinMySQL

2023-07-19 10:09:18

架構倉庫SSD

2021-05-14 06:15:48

SpringAware接口

2020-09-22 12:00:23

Javahashmap高并發

2021-05-20 08:34:03

CDN原理網絡

2021-02-19 08:20:42

JWT網絡原理
點贊
收藏

51CTO技術棧公眾號

99精品国产在热久久| 国产精品毛片视频| 国产精品三级视频| 91aaaa| 日本三级网站在线观看| 综合综合综合综合综合网| 日本高清无吗v一区| 久久免费看毛片| 性xxxx视频| 狠狠狠色丁香婷婷综合激情| 国内精品视频久久| 91香蕉视频污在线观看| 精品国产一区二区三区成人影院 | 免费在线观看日韩| 国产欧美日韩精品高清二区综合区| 欧美日韩综合不卡| 欧美成人高潮一二区在线看| a天堂在线资源| 成人性视频免费网站| 国产精品久久久久久久久久免费 | 一本一道久久a久久精品 | 欧美三电影在线| 男人和女人啪啪网站| 国产黄色小视频在线| 久久久综合激的五月天| 99c视频在线| 亚洲午夜激情视频| 另类激情亚洲| 97激碰免费视频| 中文字幕资源站| 日韩精品诱惑一区?区三区| 亚洲成人教育av| 日本中文字幕在线不卡| 另类中文字幕国产精品| 精品欧美国产一区二区三区| 欧美 另类 交| 色网站在线看| 国产精品水嫩水嫩| 欧美主播一区二区三区美女 久久精品人| 精品国产伦一区二区三| 精品一二线国产| 日韩av色在线| 欧美精品韩国精品| 国产免费成人| 777午夜精品福利在线观看| 国产福利久久久| 精久久久久久| 久久久久久久国产精品视频| 男人与禽猛交狂配| 欧美日本一区二区高清播放视频| 精品国产一区二区三区久久狼5月 精品国产一区二区三区久久久狼 精品国产一区二区三区久久久 | 一区二区三区欧美日韩| 91麻豆天美传媒在线| 精品国产99久久久久久| 亚洲人成网站精品片在线观看| 亚洲欧美久久久久一区二区三区| 精品推荐蜜桃传媒| 国产视频一区不卡| 亚洲精品高清视频| 国产在线高清理伦片a| 亚洲欧美一区二区三区久本道91| 色呦呦网站入口| 午夜小视频福利在线观看| 亚洲激情中文1区| 日韩网站在线免费观看| 九色porny自拍视频在线观看| 精品久久久久久久久国产字幕| 久在线观看视频| 亚洲成人不卡| 538在线一区二区精品国产| 亚洲成人av免费观看| 成人精品毛片| 亚洲天堂免费观看| 国产白丝一区二区三区 | 国产精品美女在线观看直播| 亚洲精品乱码久久久久久金桔影视 | 天天干天天爽天天射| 国产麻豆一区二区三区| 亚洲精品一区二区三区香蕉| 中文字幕一区二区久久人妻网站| 国产调教一区二区三区| 久久精品亚洲热| 日本特黄特色aaa大片免费| 国产一区二区三区的电影| 国产aⅴ夜夜欢一区二区三区| 在线中文字幕网站| 不卡区在线中文字幕| 日本精品国语自产拍在线观看| 日本综合在线| 亚洲国产人成综合网站| 无码人妻精品一区二区三区66| 福利一区三区| 亚洲久久久久久久久久| 成人免费精品动漫网站| 99在线|亚洲一区二区| 国产精品久久激情| 老熟妇高潮一区二区高清视频| 久久精品视频免费| 国产又粗又长又爽视频| 裤袜国产欧美精品一区| 日韩你懂的在线观看| 欧美性猛交xxxx乱| 亚洲午夜一区| 国产一区玩具在线观看| 亚洲av成人精品毛片| 1000部国产精品成人观看| 欧美一级在线看| 精品视频一区二区三区在线观看| 日韩精品极品在线观看| 综合五月激情网| 青青青伊人色综合久久| 激情视频一区二区| 性网站在线观看| 欧美人牲a欧美精品| 欧美色图亚洲激情| 亚洲激情av| 成人av番号网| 触手亚洲一区二区三区| 欧美日韩精品在线播放| 国产欧美视频一区| 亚洲综合婷婷| 成人网中文字幕| se在线电影| 色综合天天综合狠狠| 亚洲图片综合网| 尤物在线精品| 99久久一区三区四区免费| 日本中文字幕视频在线| 欧美性大战久久久久久久 | 国内成人精品| 欧美中文字幕在线| 亚洲欧洲国产综合| 亚洲成a天堂v人片| 99riav国产精品视频| 欧美激情自拍| 亚洲影视九九影院在线观看| 日韩在线免费电影| 欧美日韩综合在线免费观看| 中文字幕有码在线播放| 日韩影院免费视频| 日本在线成人一区二区| 九九热线视频只有这里最精品| 日韩精品999| 亚洲天堂视频网站| 26uuu精品一区二区三区四区在线| 久操网在线观看| 国产成人tv| 91av视频在线观看| 日韩porn| 欧洲在线/亚洲| 手机看片日韩av| 久久99国产精品久久99果冻传媒| 亚洲欧美影院| 高清一区二区三区av| 久久国产精品久久精品| 亚洲第九十九页| 午夜在线电影亚洲一区| 右手影院亚洲欧美| 日韩av中文字幕一区二区| 五月天丁香综合久久国产| 精品美女一区| 九九热99久久久国产盗摄| 丰满人妻妇伦又伦精品国产| 午夜欧美在线一二页| 欧美bbbbb性bbbbb视频| 丝袜亚洲精品中文字幕一区| 亚洲国产精品视频一区| 精品中文字幕一区二区三区| 久久久伊人日本| 国产中文字幕在线看| 欧美理论片在线| 国产亚洲成人精品| 久久一区二区三区国产精品| 国产精品视频分类| 欧美粗暴jizz性欧美20| 久久久久久久久久久久久久久久av| 成人av免费电影网站| 色噜噜狠狠色综合网图区| 国内老熟妇对白hdxxxx| 精品女同一区二区三区在线播放| 精品国产成人亚洲午夜福利| 国产综合色在线| 99精品在线免费视频| 日韩欧美1区| 国产一区在线免费观看| a∨色狠狠一区二区三区| 欧美丰满少妇xxxxx| 国产永久免费高清在线观看| 91精品欧美福利在线观看| 中文字幕在线观看免费视频| 亚洲国产成人午夜在线一区| 91超薄肉色丝袜交足高跟凉鞋| 久久亚洲视频| 国产成a人亚洲精v品在线观看| 亚洲v天堂v手机在线| 亚洲综合中文字幕在线观看| 中文字幕人成乱码在线观看| 日韩视频免费在线| 精品av中文字幕在线毛片| 日韩视频免费观看高清在线视频| 国产www在线| 亚洲午夜在线观看视频在线| 你懂得视频在线观看| 99国产精品一区| 韩国三级与黑人| 欧美aaa在线| 岳毛多又紧做起爽| 狠狠色狠狠色综合日日tαg| 偷拍视频一区二区| 免费看日本一区二区| y111111国产精品久久婷婷| 成人午夜sm精品久久久久久久| 97在线精品国自产拍中文| 黄在线免费看| 中文字幕在线视频日韩| 牛牛澡牛牛爽一区二区| 亚洲福利视频网| 丰满人妻一区二区三区四区53| 欧美日本不卡视频| 中文字幕理论片| 在线观看视频一区二区| 91video| 欧美日韩国产精品一区二区不卡中文| 欧美日韩在线视频免费| 亚洲欧洲无码一区二区三区| 香蕉久久久久久久| 国产目拍亚洲精品99久久精品| 9.1成人看片| 99这里只有精品| 欧美xxxxx精品| 懂色av一区二区三区免费看| 91视频免费入口| 国产精品中文字幕日韩精品 | 丰满亚洲少妇av| 欧美高清精品一区二区| 国产在线视视频有精品| 91高清国产视频| 美女视频一区在线观看| 性欧美videossex精品| 免费观看在线综合| 久草福利视频在线| 日本美女一区二区| 污污网站免费看| 久久精品国产成人一区二区三区 | 国产精品亚洲一区二区在线观看| 国产女精品视频网站免费| 天然素人一区二区视频| 国产精品丝袜一区二区三区| 亚洲黑人在线| 亚洲在线www| 91精品啪在线观看国产手机| 国产乱码精品一区二区三区日韩精品| 成人看片黄a免费看视频| 久久福利电影| jvid福利在线一区二区| 亚洲午夜精品久久| 亚洲免费二区| 少妇人妻大乳在线视频| 在线视频精品| 麻豆一区二区三区视频| 紧缚奴在线一区二区三区| 国产亚洲色婷婷久久| jvid福利写真一区二区三区| wwwwww日本| 中文字幕在线一区免费| 青青草成人免费| 精品日韩美女的视频高清| 无码人妻精品一区二区三区9厂 | 手机在线电影一区| 99久热在线精品视频| 亚洲国产黄色| 天天天干夜夜夜操| 国产麻豆日韩欧美久久| 欧美一区二区免费在线观看| 国产日韩欧美麻豆| 老湿机69福利| 欧美日韩中文字幕综合视频| 中文字幕一区二区三区免费看| 日韩一级视频免费观看在线| 天天躁日日躁狠狠躁伊人| 中文字幕精品一区二区精品| 香蕉成人app免费看片| 欧洲成人在线观看| 久久丁香四色| 日本不卡在线播放| 午夜精品影院| 一本久道综合色婷婷五月| 国产美女娇喘av呻吟久久| av无码av天天av天天爽| 成人免费视频在线观看| 永久免费无码av网站在线观看| 欧美日本一道本| 五月婷婷丁香花| 久久久精品久久久久| 涩涩视频在线播放| 91久久久在线| 国内精品久久久久久99蜜桃| 国产爆乳无码一区二区麻豆 | 图片区偷拍区小说区| 国产欧美一区二区在线观看| 日本a在线观看| 欧美乱妇20p| 黄色av免费在线观看| 久久久久久久电影一区| 亚洲爽爆av| 日韩久久不卡| 在线综合亚洲| 超级砰砰砰97免费观看最新一期 | 欧产日产国产v| 欧美偷拍一区二区| 欧美日韩国产亚洲沙发| 久久乐国产精品| 日韩高清一区| 一本色道婷婷久久欧美| 麻豆九一精品爱看视频在线观看免费| 日本一区二区三区在线免费观看| 国产欧美视频一区二区三区| 日韩成人一区二区三区| 日韩欧美一区在线观看| 欧美jizz18性欧美| 国产精品久久久久久久久久久久久久 | 尤物视频在线看| 成人免费网站在线看| 欧美午夜精品一区二区三区电影| 丝袜老师办公室里做好紧好爽| 成人免费看视频| 国产性一乱一性一伧一色| 日韩三级av在线播放| 动漫一区在线| 91色琪琪电影亚洲精品久久| 久久蜜桃av| 天天干天天av| 成人免费一区二区三区在线观看| 亚洲自拍第二页| 中文字幕亚洲欧美日韩高清| 日韩经典一区| 色一情一区二区三区四区| 久久在线91| 鲁丝一区二区三区| 在线观看视频一区| 98在线视频| 成人在线国产精品| 午夜精彩国产免费不卡不顿大片| 日韩欧美中文视频| 悠悠色在线精品| 韩国av在线免费观看| 久久人人爽人人| 免费成人蒂法| 粉嫩虎白女毛片人体| 欧美国产1区2区| 国产一区二区三区黄片| 久久精品最新地址| 亚洲精品v亚洲精品v日韩精品| 久久国产精品免费观看| 国产99久久精品| 日本黄色片视频| 亚洲人成免费电影| 久久人体av| 99热都是精品| 不卡视频在线观看| 亚洲不卡在线视频| 日韩在线观看网址| 6080亚洲理论片在线观看| 久色视频在线播放| 国产视频一区二区三区在线观看| 亚洲一区中文字幕在线| 久久99国产精品久久久久久久久| 波多野结衣欧美| 欧美精品第三页| 日韩毛片一二三区| 免费a视频在线观看| 欧美又大又硬又粗bbbbb| 青草国产精品| 亚洲熟妇一区二区| 一本色道久久综合亚洲aⅴ蜜桃 | 亚洲激情 欧美| 欧洲精品一区二区| 2024最新电影在线免费观看| 国产精品中出一区二区三区| 日韩av高清在线观看| 国产一区二区播放| 亚洲人成欧美中文字幕| 91精品一久久香蕉国产线看观看| 18禁裸男晨勃露j毛免费观看| 久久综合丝袜日本网| 国产影视一区二区| 88国产精品欧美一区二区三区| 欧美成人精品一区二区三区在线看| 台湾佬美性中文| 在线免费观看日本一区| 视频在线观看入口黄最新永久免费国产| 精品欧美国产| 国产在线视频一区二区| 国产一级18片视频| 欧美另类高清videos| 精品国产一区二区三区小蝌蚪| 少妇伦子伦精品无吗| 欧美在线一区二区三区| 成人超碰在线| 免费在线观看污污视频|