說起分佈式自增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僅僅是19位的整形數字,正好契合mysql的bigint數據類型,簡直完美。

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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章