以固定步長生成乙個序列 唯一ID生成演算法剖析

2021-10-14 17:35:45 字數 3707 閱讀 7004

在業務開發中,大量場景需要唯一id來進行標識:使用者需要唯一身份標識;商品需要唯一標識;訊息需要唯一標識;事件需要唯一標識…等等,都需要全域性唯一id,尤其是分布式場景下。

唯一id有哪些特性或者說要求呢?按照我的分析有以下特性:

一般來說,常用的唯一id生成方法有這些:uuid:資料庫自增id:雪花演算法uuid

uuid全稱為:universally unique identifier(通用唯一識別碼),有的地方也稱作guid(globally unique identifier),實際上guid指微軟對於uuid標準的實現的實現。

uuid演算法的目的是為了生成某種形式的全域性唯一id來標識系統中的任一元素,尤其在分布式環境下,該id需要不依賴中心認證即可自動生成全域性唯一id。

其優勢有:

而缺點為:uuid的標準形式為32個十六進製制數組成的字串,且分隔為五個部分,如:467e8542-2275-4163-95d6-7adc205580a9各部分的數字個數為:8-4-4-4-12根據需要不同,標準提供了不同的uuid版本以供使用,分別對應於不同的uuid生成規則:

版本2 - 分布式安全的uuid:將版本1的時間戳前四位換為posix的uid或gid,很少使用

版本4 - 基於隨機數的uuid:基於隨機數或偽隨機數生成,

版本5 - 基於名字空間的uuid(sha1版):將版本3的雜湊演算法改為sha1

版本1 - 基於時間的uuid:版本2 - 分布式安全的uuid:版本3 - 基於名字空間的uuid(md5版):版本4 - 基於隨機數的uuid:版本5 - 基於名字空間的uuid(sha1版):總得來說:以版本1 - 基於時間的uuid為例先梳理uuid的結構:

uuid為32位的十六機制數,因此實際上是16-byte (128-bit),各位分別為:

時間值:在基於時間的uuid中,時間值是乙個60位的整型值,對應utc的100ns時間間隔計數,因此其支援支援一台機器每秒生成10m次。在uuid中,將這60位放置到了15~08這8-byte中(除了09位有4-bit的版本號內容)。版本號:版本號即上文所說的五個版本,在五個版本的uuid中,都總是在該位置標識版本,佔據 4-bit,分別以下列數字表示:

變體值:表明所依賴的標準(x表示可以是任意值):

節點值:

在基於時間的uuid中,節點值佔據了05~00的48-bit,由機器的mac位址構成。

如果機器有多個mac位址,則隨機選其中乙個;

如果機器沒有mac位址,則採用(偽)隨機數。

了解了基於時間的uuid結構及生成規則後,再看看其他版本的uuid生成規則:

將基於時間的uuid中時間戳前四位換為posix的uid或gid,其餘保持一致。

將命名空間 (如dns、url、oid等) 及名字轉換為位元組序列;

通過md5/sha1雜湊演算法將上述位元組序列轉換為16位元組雜湊值 (md5雜湊不再推薦,sha1雜湊的20位只使用其15~00位);

將雜湊值的 3~0 位元組置於uuid的15~12位;

將雜湊值的 5~4 位元組置於uuid的11~10位;

將雜湊值的 7~6 位元組置於uuid的09~08位,並用相應版本號覆蓋第9位的高4位 (同版本1位置);

將雜湊值的 8 位元組置於uuid的07位,並用相應變體值覆蓋其高2位 (同版本1位置);

將雜湊值的 9 位元組置於uuid的06位 (原時鐘序列位置);

將雜湊值的 15~10 位元組置於uuid的05~00位 (原節點值位置)。

生成16byte隨機值填充uuid。重複機率與隨機數產生器的質量有關。若要避免重複率提高,必須要使用基於密碼學上的假隨機數產生器來生成值才行;

將變體值及版本號填到相應位置。

// 版本 1 - 基於時間的uuid:gen_uuid() // 版本4 - 基於隨機數的uuid:gen_uuid() // 版本5 - 基於名字空間的uuid(sha1版):gen_uuid(name)
(左滑檢視完整**)

資料庫自增id

因此這裡給出兩種優化方案。

這其實都是在需求與方案間的權衡,根據需求來選擇最適合的方式。如果要使用單台機器做id生成,避免固定步長帶來的擴容問題,可以每次批量生成一批id給不同的機器去慢慢消費,這樣資料庫的壓力也會減小到n分之一,且故障後可堅持一段時間。

雪花演算法

定義乙個64bit的數,對指定機器 & 同一時刻 & 某一併發序列,是唯一的,其極限qps約為400w/s。其格式為:

由於雪花演算法是強依賴於時間的,在分布式環境下,如果發生時鐘回撥,很可能會引起id衝突的問題。解決方案有:方案對比

可以發現,常用的分布式唯一id生成思路基本是利用乙個長串數字或字串,將其分割成多個部分,分別記錄時間資訊、機器/名字資訊、隨機資訊、序列資訊等。時間資訊部分決定了該策略能使用的時長,機器/名字資訊支援了分布式環境下的獨自生成唯一id與識別能力,序列資訊保證了事件的順序記錄以及同一時間單位下的併發數,而隨機資訊則加大了id整體的不可識別性。

實際上如果現有的方法依然不能滿足,我們完全可以依據自身業務和發展需求,來自行決定使用何種策略生成唯一id。各種方案都有其優缺點,技術的使用沒有絕對的好壞之分,主要在於是否適合使用場景:

對於最初我們定義的唯一id特性,各方案的對比如下:

參考uuid演算法分析

關於uuid的二三事

uuid唯一資源命名空間的來龍去脈

uuid是如何保證唯一性的?

如果再有人問你分布式 id,這篇文章丟給他

分布式唯一id的幾種生成方案

leaf——美團點評分布式id生成系統

分布式系統:lamport 邏輯時鐘

生成乙個唯一的id

php uniqid 函式可用於生成不重複的唯一識別符號,該函式基於微秒級當前時間戳。在高併發或者間隔時長極短 如迴圈 的情況下,會出現大量重複資料。即使使用了第二個引數,也會重複,最好的方案是結合md5函式來生成唯一id。php uniqid 生成不重複唯一標識方法一 這種方法會產生大量的重複資料...

PHP如何隨機生成乙個唯一的id

開門見山 echo str md5 uniqid mt rand true 如上就可以生成乙個唯一的標識了,思路如下 mt rand 使用 mersenne twister 演算法返回隨機整數。如果沒有提供可選引數 min 和 max,mt rand 返回 0 到 rand max 之間的偽隨機數。...

生成乙個全球唯一識別符號

delphi varfguid tguid begin createguid fguid edt1.text guidtostring fguid edt1.text edt1.text inttostr length edt1.text edt1.text copy guidtostring fg...