六大全局唯一ID生成算法策略,效率對比。 總有一款是你的菜

前言

ID在程序設計中,無處不在,至關重要。

  • 分佈式鎖中,我們會用唯一ID宣誓鎖的歸屬。
  • 數據庫中用主鍵ID記錄每一行並綁定Data。
  • 分庫分表的系統中,用ID生成,來保證全局唯一等等

自己做下總結。


分佈式ID的要求

  • UNIQ 唯一性:ID,ID 要的就是唯一
  • HP 高性能:生成ID的服務,不能成爲瓶頸
  • HA 高可用:保證高可用,如果ID是訂單ID,突然ID服務宕機,影響全局交易就不好了
  • 趨勢:遞增還是隨機,看場景需要

知道了基本要求,下面開始介紹各種策略,並分析一下他們的是否達到了這些要求。


1:UUID

uuid例子:f1c09159-97c3-4ac1-8cb1-2d820a8eeb05

經過計算,每秒生成10億個UUID,100年不間斷,有50%的可能產生一個衝突
UUID的碰撞只存在理論上的可能,現實中可以忽略不計。

我們來看下性能:單線程1秒 百萬量級。
在這裏插入圖片描述
因爲是全局唯一,所以我們常常用來做分佈式鎖的唯一標識,保證獲得鎖和釋放鎖的是同一個人。

唯一性:滿足
高性能:滿足
高可用:滿足
趨勢:隨機


2:Redis incr

redis作爲單線程的nosql系統。
使用incr就高性能的生成唯一的單調遞增ID。
在這裏插入圖片描述
唯一性:基本滿足(依賴可用性)
高性能:基本滿足
高可用:不滿足,存於內存,要考慮持久化和容災。集羣模式下重連還要進行主從複製,等待時間比較長。
趨勢:單調遞增


3:Snowflake雪花算法

下面說一說大名鼎鼎的Twitter的雪花算法。
算法結果是一個長整型 Long
大佬們慣用的 位存儲轉基本類型。

基本原理如下圖,一眼就描述清楚了。
在這裏插入圖片描述

  • 0位 符號位:0 正數
  • 1~41位:毫秒時間戳,減一個固定開始時間,可以延長使用時間
  • 42~51位:業務ID
  • 52~64位:序列號 自增 2^12 = 4096

每毫秒1024個業務,每個業務能容納4096個ID。
生成效率,30W/s
在這裏插入圖片描述

唯一性:滿足
高性能:滿足
高可用:滿足
趨勢:趨勢遞增

當然這是一種思路
在企業開發中,可以在企業開發中借鑑一下
比如我們生成訂單號就是類似的思路:630558772926921027。
出於保密,我就不具體解讀了。

雪花就沒毛病了嘛?大部分場景其實是的。不過有兩個問題,還是需要考慮:

  1. 每毫秒4096,還是可能成爲瓶頸的
  2. 雪花ID是長整型,直接用來做SQL主鍵,也是比較浪費空間的。
  3. 依賴機器時間,可能會有實現回撥

4: AUTO_INCREMENT數據庫自增

在這裏插入圖片描述
直接使用DB的AUTO_INCREMENT。就能得到遞增的ID。

唯一性:滿足
高性能:IO瓶頸哦
高可用:單機問題
趨勢:單調遞增

純粹個人玩玩可以。線上還要針對性能和可用性進行優化,所以就引入集羣模式。


5:數據庫集羣模式

要數據庫的高性能高可用,肯定要考慮集羣。

有了多態機器,就需要每個機器單獨負責生成號碼。
我們使用 初始值offset步長increment

來看配置

MySQL_1 配置:
set @@auto_increment_offset = 1;     -- 起始值
set @@auto_increment_increment = 3;  -- 步長

MySQL_2 配置:
set @@auto_increment_offset = 2;     -- 起始值
set @@auto_increment_increment = 3;  -- 步長

MySQL_3 配置:
set @@auto_increment_offset = 3;     -- 起始值
set @@auto_increment_increment = 3;  -- 步長
完美解決。
Mysql1生成:14710……
Mysql2生成:25811……
Mysql3生成:36912……

但是發現問題來了。 這裏的步長就等於機器的數量
未來如果要擴容就非常困難了。。。

唯一性:滿足
高性能:滿足
高可用:集羣高可用
趨勢:趨勢遞增

缺點:

  1. 無法擴容。定好步長和初始值後,就涼了。再次擴容,一般需要停滯服務,併產生號段空洞。
  2. 成本高

爲了解決性能瓶頸和處理擴容問題


6:Segment 號段模式

集羣模式,每個ID和數據庫交互一次。
而號段模式,借鑑單次步長step,就是一次SQL交互取出的ID代表了一個號段。
如id=1,step=1000. 代表數字[1,1000].
獲去1個ID的讀寫數據庫的頻率,從1減小到了1/step。

先看例子,我直接使用美團的id生成器Leaf來說明。
先上表。
DB數據

CREATE DATABASE leaf
CREATE TABLE `leaf_alloc` (
  `biz_tag` varchar(128)  NOT NULL DEFAULT '', -- your biz unique name
  `max_id` bigint(20) NOT NULL DEFAULT '1', // 當前被使用的最大ID
  `step` int(11) NOT NULL, // 單步步長
  `description` varchar(256)  DEFAULT NULL,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB;

insert into leaf_alloc(biz_tag, max_id, step, description) values('leaf-segment-test', 1, 2000, 'Test leaf Segment Mode Get Id')

在這裏插入圖片描述
使用leaf-segment-test2 生效ID的效果
在這裏插入圖片描述
可以看到 max_id = 201 而 step=100時。
此時內存中(segment對象)存儲了[101,200] 共100個ID。 進行自增的發號
在這裏插入圖片描述
這樣每100個號碼才IO一次。 瓶頸解決
在這裏插入圖片描述
同時Leaf爲了防止IO獲取號段時,導致的服務不可用,採用提前加載的方式處理。稱爲雙buffer優化
在這裏插入圖片描述

當取到309時
在這裏插入圖片描述
db效果在這裏插入圖片描述

再取幾個
在這裏插入圖片描述
db效果
在這裏插入圖片描述

可以看到這裏提前開闢了號段。

	// 關鍵的判斷: 剩餘 < 總數的90%(發了10%,就會去開闢新號段)。 
	segment.getIdle() < 0.9 * segment.getStep()

取得的新號段,會在Buffer中存儲,可以看到源碼中,Segment是數組。通過通過維護好currentPos實現提前號段的buffer~ 女少!
在這裏插入圖片描述

唯一性:滿足
高性能:滿足
高可用:集羣高可用
趨勢:趨勢遞增

同時拓展只需新增biz_tag 也比較方便。 我們公司交易的分庫分表就是採用Segment來實現的。

TIPS:第三方框架

美團Leaf:世界上沒有兩片完全相同的樹葉。
https://github.com/Meituan-Dianping/Leaf/blob/feature/spring-boot-starter/README_CN.md

百度:https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md

其實技術選型不是找到最極致的,而是找到最合適的。
所以公司層面,一般都會定製自己的分佈式ID來滿足相關的業務需求。

溜了溜了

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