在分布式系統中,生成全域性唯一id,有很多種方案,但是在這多種方案中,每種方案都有有缺點,下面我們之針對通過常用資料庫來生成分布式id的方案,其它方法會在其它文中討論:
這裡我們討論mysql生成id。因為mysql本身可以auto_increment和auto_increment_offset來保證id自增,很自然地,我們會想到借助這個特性來實現這個功能。
全域性id生成方案裡採用了mysql自增長id的機制(auto_increment + replace into + myisam)。乙個生成64位id方案具體實現是這樣的:
先建立單獨的資料庫(eg:ticket),然後建立乙個表:
create表建立之後我們要設定乙個初始值,比如100000,執行select * from tickets64,查詢結果就是這樣的:table
tickets64 (
id bigint(20) unsigned not
null
auto_increment,
stub
char(1) not
null
default'',
primary
key(id),
unique
keystub (stub)
) engine
=myisam
每當我們的應用需要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:假設我們要部署n臺機器,步長需設定為n,每台的初始值依次為0,1,2...n-1那麼整個架構就變成了如下圖所示:auto-increment-increment = 2auto-increment-offset = 1ticketserver2:
auto-increment-increment = 2auto-increment-offset = 2
這種架構貌似能夠滿足效能的需求,但有以下幾個缺點:
這種方案大致來說是一種以劃分命名空間(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欄位上...