天天看點

說起分布式自增ID隻知道UUID?SnowFlake(雪花)算法了解一下(Python3.0實作)

原文轉載自「劉悅的技術部落格」https://v3u.cn/a_id_155

但凡說起分布式系統,我們肯定會對一些海量級的業務進行分拆,比如:使用者表,訂單表。因為資料量巨大一張表完全無法支撐,就會對其進行分庫分表。但是一旦涉及到分庫分表,就會引申出分布式系統中唯一主鍵ID的生成問題,當我們使用mysql的自增長主鍵(auto_increment)時,充分感受到了它的好處:整個系統ID唯一,ID是數字類型,而且是趨勢遞增的,ID簡短,查詢效率快,在分布式系統中顯然由于單點問題無法使用mysql自增長了,此時需要别的解決方案來支撐分布式業務。

首先映入腦海的一定是uuid

>>> import uuid
>>> print(uuid.uuid1())  
d13a0096-abca-11ea-8997-acbc32785ec1  
  

           

客觀地說,如果一定要用uuid生成訂單号這類東西也能湊合用,但是它有着罄竹難書的“罪行”:肉眼可見,它是無序的;長度是64位數字字母随機組合的字元串,占用空間巨大;完全不具備業務屬性,也就是說使用uuid你完全無法推算出它到底是幹嘛的;因為無序,是以趨勢遞增就更不用指望了;是以用uuid生成訂單号就是自殺行為,适合它的是類似生成token令牌的場景。

那麼我們就要說起業界鼎鼎有名的SnowFlake(雪花算法)發号器了。 Twitter-Snowflake算法産生的背景相當簡單,為了滿足Twitter每秒上萬條消息的請求,每條消息都必須配置設定一條唯一的id,這些id還需要一些大緻的順序,讓twitter可以通過一定的索引來進行檢索,而在Twitter龐大的分布式系統中不同機器産生的id必須又必須不同。

說起分布式自增ID隻知道UUID?SnowFlake(雪花)算法了解一下(Python3.0實作)

它的好處顯而易見,不僅全局唯一,并且有序按時間遞增,同時占用空間少,生成的id僅僅是19位的整形數字,正好契合mysql的bigint資料類型,簡直完美。

為啥它叫做Snowflake(雪花)算法?因為每個人都知道沒有兩片一樣的雪花,這一事實源于晶體在天空中形成的方式。雪是一團冰晶,在大氣中形成,并在它們下落時保持其形狀。雪花形成于大氣冷到能阻止它們融化變成雨或雨夾雪的時候。盡管雲中的溫度和濕度是不均勻的,但是在雪花大小的範圍内,這些變量大約都是常數,這就是雪花的生長通常是對稱的原因。另一方面,塔夫茨大學(Tufts University)化學家瑪麗·簡·舒爾茨(Mary Jane Shultz)指出:每片雪花都受到風,日光和其他變量變化的影響。她解釋說,由于每個雪晶都到雲層紊亂的影響,它們的形式都略有不同。

而Snowflake的邏輯也非常簡單,雪花算法生成64位的二進制正整數,然後轉換成10進制的數。64位二進制數由如下部分組成:

說起分布式自增ID隻知道UUID?SnowFlake(雪花)算法了解一下(Python3.0實作)

1位辨別符:始終是0

41位時間戳:41位時間戳不是存儲目前時間的時間戳,而是存儲時間截的內插補點(目前時間截 - 開始時間截 )得到的值,這裡的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程式來指定的

10位機器辨別碼:可以部署在1024個節點,如果機器分機房(IDC)部署,這10位可以由 5位機房ID + 5位機器ID 組成

12位序列:毫秒内的計數,12位的計數順序号支援每個節點每毫秒(同一機器,同一時間截)産生4096個ID序号

看到時間戳,就可以聯想到它的缺陷,也就是它依賴機器的時鐘,如果伺服器時鐘回撥,可能會導緻重複ID生成。

這裡我們用Python3.0來生成SnowFlake生成的唯一id

首先安裝庫

pip3 install pysnowflake
           

安裝完成後,就可以在本地指令行啟動snowflake服務

snowflake_start_server --worker=1
           

這裡的worker就是目前節點的辨別,此時編寫代碼就可以列印出目前用戶端使用的snowflake的服務資訊

import snowflake.client  
print(snowflake.client.get_stats())  
  
{'dc': 0, 'worker': 1, 'timestamp': 1591871273195, 'last_timestamp': 550281600000, 'sequence': 0, 'sequence_overload': 0, 'errors': 0}
           

當然了,如果一台伺服器上起了很多節點服務,也可以指定相關的節點進行裝載

host = '127.0.0.1'  
port = 30001  
snowflake.client.setup(host, port)
           

随後我們究竟可以根據該服務生成唯一id了

import snowflake.client  
  
print(snowflake.client.get_guid())

4368750411956359169 
           

可以看到這些id很明顯帶有遞增的連續性,有的人會問了,假設我搭建了上千個節點的分布式系統,此時接口接到參數id,我怎麼判斷該id的訂單資訊存儲在那個節點中呢?

其實很容易就可以判斷,從SnowFlake的算法結構入手,本身就是二進制轉換十進制的整形,現在我們反着進行解析即可,這裡以這個19位的id為例子:4368750411956359169

首先将其轉換為二進制

print(bin(4368750411956359169))  
0b11110010100000111010110101101001100001000000000001000000000001
           

可以看到上文所述的第一位是辨別符,此後是41位的時間戳,緊接着10位的節點辨別碼,最後12位的遞增序列,從後面數12位是:000000000001,再數5位是:00001 這5位就是某個節點的存儲辨別,但是它目前是二進制,我們再将它轉換為十進制

print(int('00001',2))  
1
           

可以看到,轉換結果顯示該id存儲在節點1的資料庫中,如此就具備了相當強的業務屬性,通過反推邏輯我們可以快速準确的定位到資料的具體存儲位置進而進行查詢。

結語:

其實關于分布式唯一id的解決方案,也不僅僅隻有uuid或者snowflake,像redis的incr原子性操作自增,亦或者Mongodb極具特色的_objectid的生成方式,專為分布式而設計的ID生成方案。都是可以參考的解決方案,但是方案總歸是方案,總有其自身的特點和缺陷,這就需要根據實際應用場景而具體問題進行具體分析了。

原文轉載自「劉悅的技術部落格」 https://v3u.cn/a_id_155

繼續閱讀