基於資料庫構建分布式的ID生成方案

2021-09-06 20:16:30 字數 3781 閱讀 1034

在分布式系統中,生成全域性唯一id,有很多種方案,但是在這多種方案中,每種方案都有有缺點,下面我們之針對通過常用資料庫來生成分布式id的方案,其它方法會在其它文中討論:

這裡我們討論mysql生成id。因為mysql本身可以auto_increment和auto_increment_offset來保證id自增,很自然地,我們會想到借助這個特性來實現這個功能。

全域性id生成方案裡採用了mysql自增長id的機制(auto_increment + replace into + myisam)。乙個生成64位id方案具體實現是這樣的: 

先建立單獨的資料庫(eg:ticket),然後建立乙個表:

create

table

tickets64 (

id bigint(20) unsigned not

null

auto_increment,

stub

char(1) not

null

default'',

primary

key(id),

unique

keystub (stub)

) engine

=myisam

表建立之後我們要設定乙個初始值,比如100000,執行select * from tickets64,查詢結果就是這樣的:

每當我們的應用需要id的時候就會做如下操作,呼叫如下儲存過程:

begin

;replace

into tickets64 (stub) values ('a'

);select

last_insert_id();

commit;

架構如圖:

這樣我們就能拿到不斷增長且不重複的id了。 

這種方案的優缺點如下:

優點: 缺點:

對於mysql效能問題,可用如下方案解決:在分布式系統中我們可以多部署幾台機器,每台機器設定不同的初始值,且步長和機器數相等。比如有兩台機器。設定步長step為2,ticketserver1的初始值為1(1,3,5,7,9,11...)、ticketserver2的初始值為2(2,4,6,8,10...)。這是flickr團隊在2023年撰文介紹的一種主鍵生成策略(ticket servers: distributed unique primary keys on the cheap )。如下所示,為了實現上述方案分別設定兩台機器對應的引數,ticketserver1從1開始發號,ticketserver2從2開始發號,兩台機器每次發號之後都遞增2。

ticketserver1:

auto-increment-increment = 2auto-increment-offset = 1ticketserver2:

auto-increment-increment = 2auto-increment-offset = 2

假設我們要部署n臺機器,步長需設定為n,每台的初始值依次為0,1,2...n-1那麼整個架構就變成了如下圖所示:

這種架構貌似能夠滿足效能的需求,但有以下幾個缺點:

這種方案大致來說是一種以劃分命名空間(uuid也算,由於比較常見,所以單獨分析)來生成id的一種演算法,這種方案把64-bit分別劃分成多段,分開來標示機器、時間等,比如在snowflake中的64-bit分別表示如下圖(來自網路)所示:

41-bit的時間可以表示(1l<<41)/(1000l*3600*24*365)=69年的時間,10-bit機器可以分別表示1024臺機器。如果我們對idc劃分有需求,還可以將10-bit分5-bit給idc,分5-bit給工作機器。這樣就可以表示32個idc,每個idc下可以有32臺機器,可以根據自身需求定義。12個自增序列號可以表示2^12個id,理論上snowflake方案的qps約為409.6w/s,這種分配方式可以保證在任何乙個idc的任何一台機器在任意毫秒內生成的id都是不同的。

這種方式的優缺點是:

優點: 缺點:

以mongdb objectid為例:

mongodb官方文件 objectid可以算作是和snowflake類似方法:

為了考慮分布式,「_id」要求不同的機器都能用全域性唯一的同種方法方便的生成它。因此不能使用自增主鍵(需要多台伺服器進行同步,既費時又費力),

因此選用了生成objectid物件的方法。

objectid使用12位元組的儲存空間,其生成方式如下:

|0|1|2|3|4|5|6 |7|8|9|10|11|

|時間戳 |機器id|pid|計數器 |

前四個位元組時間戳是從標準紀元開始的時間戳,單位為秒,有如下特性:

1 時間戳與後邊5個位元組一塊,保證秒級別的唯一性;

2 保證插入順序大致按時間排序;

3 隱含了文件建立時間;

4 時間戳的實際值並不重要,不需要對伺服器之間的時間進行同步(因為加上機器id和程序id已保證此值唯一,唯一性是objectid的最終訴求)。

機器id是伺服器主機標識,通常是機器主機名的雜湊值。

同一臺機器上可以執行多個mongod例項,因此也需要加入程序識別符號pid。

前9個位元組保證了同一秒鐘不同機器不同程序產生的objectid的唯一性。後三個位元組是乙個自動增加的計數器(乙個mongod程序需要乙個全域性的計數器),保證同一秒的objectid是唯一的。同一秒鐘最多允許每個程序擁有(256^3 = 16777216)個不同的objectid。

總結一下:時間戳保證秒級唯一,機器id保證設計時考慮分布式,避免時鐘同步,pid保證同一臺伺服器執行多個mongod例項時的唯一性,最後的計數器保證同一秒內的唯一性(選用幾個位元組既要考慮儲存的經濟性,也要考慮併發效能的上限)。

"_id"既可以在伺服器端生成也可以在客戶端生成,在客戶端生成可以降低伺服器端的壓力。

當使用資料庫來生成id效能不夠要求的時候,我們可以嘗試使用redis來生成id。這主要依賴於redis是單執行緒的,所以也可以用生成全域性唯一的id。可以用redis的原子操作 incr和incrby來實現。

可以使用redis集群來獲取更高的吞吐量。假如乙個集群中有5臺redis。可以初始化每台redis的值分別是1,2,3,4,5,然後步長都是5。各個redis生成的id為:

a:1,6,11,16,21

b:2,7,12,17,22

c:3,8,13,18,23

d:4,9,14,19,24

e:5,10,15,20,25

這個,隨便負載到哪個機確定好,未來很難做修改。但是3-5臺伺服器基本能夠滿足器上,都可以獲得不同的id。但是步長和初始值一定需要事先需要了。使用redis集群也可以方式單點故障的問題。

另外,比較適合使用redis來生成每天從0開始的流水號。比如訂單號=日期+當日自增長號。可以每天在redis中生成乙個key,使用incr進行累加。

優點:1)不依賴於資料庫,靈活方便,且效能優於資料庫。

2)數字id天然排序,對分頁或者需要排序的結果很有幫助。

缺點:1)如果系統中沒有redis,還需要引入新的元件,增加系統複雜度。

2)需要編碼和配置的工作量比較大。

參考:

基於資料庫的分布式鎖

使用場景 某大型 部署是分布式的,訂單系統有三颱伺服器響應使用者請求,生成訂單後統一存放到order info表 order info表要求訂單id order id 必須是唯一的,那麼三颱伺服器怎麼協同工作來確認order id的唯一性呢?這時候就要用到分布式鎖了。分布式鎖的要求 在了解了使用場景...

基於資料庫實現分布式鎖

多個程序 多個執行緒訪問共同元件資料庫.通過selec.for update訪問同一條資料 for update鎖定資料,其他執行緒只能等待 此時只有乙個操作可以對資料進行修改,而其他人不能夠對該資料進行修改操作,但可以檢視 select from distribute lock where bus...

分布式ID生成器

一 需求緣起 幾乎所有的業務系統,都有生成乙個唯一記錄標識的需求,例如 這個記錄標識往往就是資料庫中的主鍵,資料庫上會建立聚集索引 cluster index 即在物理儲存上以這個字段排序。這個記錄標識上的查詢,往往又有分頁或者排序的業務需求,例如 所以往往要有乙個time欄位,並且在time欄位上...