基于號段模式的分布式ID設計
這里介紹基于號段模式的分布式ID設計思路、方案

背景
對于分布式ID生成方案,很樸素的思路就是利用DB的自增主鍵進行實現(xiàn)。但其存在明顯的弊端。業(yè)務系統(tǒng)每次獲取ID都需要請求、訪問數(shù)據(jù)庫。數(shù)據(jù)庫壓力較大。如果這里的數(shù)據(jù)庫是單機,則容易發(fā)展成為整個系統(tǒng)的單點故障。一旦該DB宕機,則整個業(yè)務系統(tǒng)都將無法正常繼續(xù)
而如果這里的數(shù)據(jù)庫是采用集群的形式。比如這里有兩個DB——#1實例、#2實例。其中,#1實例自增ID的起始值、步長分別為1、2;#2實例自增ID的起始值、步長分別為2、2。則#1實例生成的ID序列則是1、3、5、7、···,#2實例生成的ID序列則是2、4、6、8、···。然后再提供一個發(fā)號服務來對外提供服務即可。業(yè)務系統(tǒng)只需請求發(fā)號服務獲取ID,而發(fā)號服務內部則會隨機選取一個數(shù)據(jù)庫實例來獲取相應的數(shù)據(jù)庫自增ID。雖然采用集群的方式進一步地提高了系統(tǒng)的可用性、解決了單點故障。但缺陷依然很明顯,拓展性非常差。目前DB集群中存在2臺實例,如果后期再增加1臺變成含3個實例的集群。此時,我們需要人工手動修改原#1、#2實例的步長。而且還需要將#3實例的起始值設置的足夠長,以避免發(fā)生ID重復的問題。甚至說在整個過程中,發(fā)號服務需要對外停止服務才能完成擴容
號段模式
所謂號段模式,則是指發(fā)號服務每次會從數(shù)據(jù)庫獲取一批ID,然后將這批ID緩存到發(fā)號服務本地。業(yè)務系統(tǒng)每次向發(fā)號服務請求ID時,后者會先判斷其本地是否還有可用的ID分配給業(yè)務系統(tǒng)。如果有則直接分配,反之則再次訪問數(shù)據(jù)庫來批量獲取ID。顯然在號段模式下,由于發(fā)號服務不用每次都請求數(shù)據(jù)庫。一方面減輕了數(shù)據(jù)庫的壓力,另一方面提高了系統(tǒng)的可用性、可靠性。同時,后續(xù)由于業(yè)務拓展、業(yè)務系統(tǒng)激增時,對基于號段模式的設計方案進行分庫分表也非常便于實現(xiàn)
具體地,當發(fā)號服務請求數(shù)據(jù)庫時,即會獲取一個號段范圍內的ID,例如[1,2000]號段表示1~2000的ID。發(fā)號服務會把這2000個ID緩存到本地。直到該業(yè)務第2001次向發(fā)號服務請求ID時,由于發(fā)號服務對于該業(yè)務的本地ID都已經被使用完畢了。故其會再次請求數(shù)據(jù)庫,獲取下一個號段范圍的ID——即[2001,4000]號段
號段模式下的數(shù)據(jù)表,典型設計如下所示。其中
service_name:用于實現(xiàn)服務隔離 business_name:用于實現(xiàn)服務下不同業(yè)務場景的隔離 current_max_id:則表示該業(yè)務當前已經分配了的最大ID值 step:表示發(fā)號服務每次批量獲取ID的數(shù)量。換言之,每次更新數(shù)據(jù)庫current_max_id的增量值即是step值 version:樂觀鎖,保證并發(fā)場景下的可靠性
-- 基于號段模式的分布式ID數(shù)據(jù)表
create table common_sequence (
`id` bigint(40) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
`service_name` varchar(255) DEFAULT NULL COMMENT '服務名稱',
`business_name` varchar(255) NOT NULL COMMENT '服務的業(yè)務名稱',
`current_max_id` bigint(40) NOT NULL COMMENT '當前最大ID值',
`step` int(4) NOT NULL COMMENT '步長',
`version` int(4) NOT NULL COMMENT '版本號',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
`modify_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',
primary key (`id`)
) comment = '分布式ID數(shù)據(jù)表';
發(fā)號服務每次請求數(shù)據(jù)庫獲取號段時的SQL語句大體如下所示
# 發(fā)號服務獲取號段時執(zhí)行的SQL
update common_sequence
set current_max_id = current_max_id + step,
version = version + 1
where service_name = #{service_name}
and business_name= #{business_name}
and version = #{version}
