ID生成策略——SnowFlake

普通程式設計師發表於2019-02-15

一、遇到問題


某個專案採用了資料庫(MySQL)自增ID作為主要業務資料的主鍵。資料庫自增ID使用簡單,自動編號,速度快,而且是增量增長,按順序存放,對於檢索非常有利。


單庫環境下,資料庫自增ID問題不大。但在分散式環境或分庫分表環境下,資料庫自增ID逐漸暴露出一些問題。例如,分庫分表的情況下保證ID唯一變得困難;訂單號等業務資料如果用資料庫自增ID,競對很容易算出大概的業務量


二、常見的ID生成策略


1、資料庫自增ID(前面提到了)


2、UUID

演算法的核心思想是結合機器的網路卡、當地時間、一個隨記數來生成UUID。

優點:本地生成,生成簡單,效能好,沒有高可用風險

缺點:長度過長,儲存冗餘,且無序不可讀,查詢效率低


3、Redis生成ID

Redis生成ID可以看做資料庫自增ID的升級版。Redis的所有命令操作都是單執行緒的,本身提供像 incr 和 increby 這樣的自增原子命令,所以能保證生成的 ID 肯定是唯一有序的。


優點:不依賴於資料庫,靈活方便,且效能優於資料庫;數字ID天然排序,對分頁或者需要排序的結果很有幫助。

缺點:如果系統中沒有Redis,還需要引入新的元件,增加系統複雜度;需要編碼和配置的工作量比較大。


考慮到單節點的效能瓶頸,可以使用 Redis 叢集來獲取更高的吞吐量。假如一個叢集中有5臺 Redis。可以初始化每臺 Redis 的值分別是1, 2, 3, 4, 5,然後步長都是 5。各個 Redis 生成的 ID 為

ID生成策略——SnowFlake


4、Twitter的snowflake演算法。


三、snowflake演算法


snowflake演算法,採用64位二進位制整數。二進位制具體位數含義如下圖。

ID生成策略——SnowFlake


1位,不用。二進位制中最高位為1的都是負數,但是我們生成的id都使用正數,所以這個最高位固定是0


41位,用來記錄時間戳(毫秒)。

如果只用來表示正整數(計算機中正數包含0),可以表示的數值範圍是:0 至 241−1,減1是因為可表示的數值範圍是從0開始算的,而不是1。

也就是說41位可以表示241−1個毫秒的值,轉化成單位年則是(241−1)/(1000∗60∗60∗24∗365)=69年


10位,用來記錄工作機器id。

可以部署在1024個節點,包括5位datacenterId和5位workerId


12位,序列號,用來記錄同毫秒內產生的不同id。

12位(bit)可以表示的最大正整數是4095,即可以用0、1、2、3、....4095這4096個數字,來表示同一機器同一時間截(毫秒)內產生的4096個ID序號

大多數人都知道這個演算法,但Twitter 利用 zookeeper 還做了很多工程上的實現,感興趣可以看

擷取git上該工程的主要檔案目錄, 

ID生成策略——SnowFlake


git工程README.md檔案中有這麼一段話

We have retired the initial release of Snowflake and working on open sourcing the next version based on Twitter-server, in a form that can run anywhere without requiring Twitter's own infrastructure services.


Twitter幾年前就停止了對這個專案的維護,新的版本也沒見著放出來。好在現有版本的核心演算法已經能夠滿足常規的需求。


當然,snowflake有眾多優點的同時也是有缺點的。


優點:

毫秒數在高位,自增序列在低位,整個ID都是趨勢遞增的。

不依賴資料庫等第三方系統,以服務的方式部署,穩定性更高,生成ID的效能也是非常高的。

可以根據自身業務特性分配bit位,非常靈活。


缺點:

強依賴機器時鐘,如果機器上時鐘回撥,會導致發號重複或者服務會處於不可用狀態。

強依賴時鐘在有些情況下很致命,我個人就遇到過伺服器剛重啟的短時間內時間沒有同步,造成生成ID出問題的情況!


四、一些改進策略


1、美團Leaf比較完美的方案


美團Leaf比較好的解決了這些問題,參看《Leaf——來自美團點評的分散式ID生成系統》

美團Leaf的方案核心有兩點

(1)依靠zookeeper實現workerId的自動化租用

(2)透過演算法解決了時鐘回撥問題

美團Leaf目前是開源軟體,可以在下載


2、一個候選人不嚴謹但成本很低的實現


我在面試中,一個候選人提出的方法也比較有意思(儘管這個方法不嚴謹)。

在redis中設定一個整數變數workerNum,初始值為0,snowflake id生成客戶端每次啟動時讀取redis中的變數,用workerNum%1024作為worker的值,然後把redis中的workerNum+1。

在idworker數量不多的情況下,這個方案一般不會出現workerId重複(因為隨著業務的迭代,一般情況下idworker過一段時間都會因為業務部署而重啟)。如果研發資源特別有限,又想使用snowflake可以考慮一下這個辦法。 

ID生成策略——SnowFlake


3、個人專案中hash分庫的解決辦法


實際使用中,有時候ID需要支援分庫分表,snowflake的預設實現對這塊支援得不夠。在業務量不大的情況下,snowflake生成的id序列號部分大多都是0,轉換為十進位制會是偶數。用這個id透過取模hash分庫,顯然不平均


萬一有這樣的需求怎麼辦呢?可以考慮藉助ID時間戳部分實現均勻分佈

(1)分庫分表邏輯使用ID中時間戳部分做取模。這個方法需要把10進位制ID轉成2進位制,然後移位,再進行計算。比較麻煩

(2)生成ID的時候把序列號部分尾數用時間戳對應的位置覆蓋。截段程式碼,這段程式碼的取值能保證ID除以128的餘數均勻分佈。 

ID生成策略——SnowFlake


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31556438/viewspace-2636137/,如需轉載,請註明出處,否則將追究法律責任。

相關文章