AliSQL开源Sequence Engine
Introduction
單調(diào)遞增的唯一值,是在持久化數(shù)據(jù)庫(kù)系統(tǒng)中常見(jiàn)的需求,無(wú)論是單節(jié)點(diǎn)中的業(yè)務(wù)主鍵,還是分布式系統(tǒng)中的全局唯一值,亦或是多系統(tǒng)中的冪等控制。不同的數(shù)據(jù)庫(kù)系統(tǒng)有不同的實(shí)現(xiàn)方法,比如MySQL提供的AUTO_INCREMENT,Oracle,SQL Server提供SEQUENCE等。
在MySQL數(shù)據(jù)庫(kù)中,如果業(yè)務(wù)系統(tǒng)希望封裝唯一值,比如增加日期,用戶等信息,AUTO_INCREMENT的方法會(huì)帶來(lái)很大的不便,在實(shí)際的系統(tǒng)設(shè)計(jì)的時(shí)候, 也存在不同的折中方法,比如:
- 序列值由Application或者Proxy來(lái)生成,不過(guò)弊端很明顯,狀態(tài)帶到應(yīng)用端,增加了擴(kuò)容和縮容的復(fù)雜度。
- 序列值由數(shù)據(jù)庫(kù)通過(guò)模擬的表來(lái)生成,但需要中間件來(lái)封裝和簡(jiǎn)化獲取唯一值的邏輯。
AliSQL自主實(shí)現(xiàn)了SEQUENCE ENGINE,通過(guò)引擎的設(shè)計(jì)方法,盡可能的兼容其他數(shù)據(jù)庫(kù)的使用方法,簡(jiǎn)化獲取序列值復(fù)雜度。
Github開(kāi)源地址:https://github.com/alibaba/AliSQL
Description
AliSQL開(kāi)源的SEQUENCE,實(shí)現(xiàn)了MySQL存儲(chǔ)引擎的設(shè)計(jì)接口,但底層的數(shù)據(jù)仍然使用現(xiàn)有的存儲(chǔ)引擎,比如InnoDB或者M(jìn)yISAM來(lái)保存持久化數(shù)據(jù),以便盡可能的保證現(xiàn)有的外圍工具比如XtraBackup等工具的兼容,所以SEQUENCE ENGINE僅僅是一個(gè)邏輯引擎。
對(duì)sequence對(duì)象的訪問(wèn)通過(guò)SEQUENCE handler接口,這一層邏輯引擎主要實(shí)現(xiàn)NEXTVAL的滾動(dòng),CACHE的管理等,最后透?jìng)鹘o底層的基表數(shù)據(jù)引擎,實(shí)現(xiàn)最終的數(shù)據(jù)訪問(wèn)。
下面我們透過(guò)語(yǔ)法來(lái)看下AliSQL SEQUENCE的使用。
Syntax
1. CREATE SEQUENCE Syntax:
CREATE SEQUENCE [IF NOT EXISTS] schema.sequence_name[START WITH <constant>][MINVALUE <constant>][MAXVALUE <constant>][INCREMENT BY <constant>][CACHE <constant> | NOCACHE][CYCLE | NOCYCLE];SEQUENCE OPTIONS:
-
START
Sequence的起始值 -
MINVALUE
Sequence的最小值,如果這一輪結(jié)束并且是cycle的,那么下一輪將從MINVALUE開(kāi)始 -
MAXVALUE
Sequence的最大值,如果到最大值并且是nocycle的,那么將會(huì)得到以下報(bào)錯(cuò):
ERROR HY000: Sequence 'db.seq' has been run out. -
INCREMENT BY
Sequence的步長(zhǎng) -
CACHE/NOCACHE
Cache的大小,為了性能考慮,可以設(shè)置cache的size比較大,但如果遇到實(shí)例重啟,cache內(nèi)的值會(huì)丟失 -
CYCLE/NOCYCLE
表示sequence如果用完了后,是否允許從MINVALUE重新開(kāi)始
例如:
create sequence sstart with 1minvalue 1maxvalue 9999999increment by 1cache 20cycle;2. SHOW SEQUENCE Syntax
SHOW CREATE [TABLE|SEQUENCE] schema.sequence_name;CREATE SEQUENCE schema.sequence_name (`currval` bigint(21) NOT NULL COMMENT 'current value',`nextval` bigint(21) NOT NULL COMMENT 'next value',`minvalue` bigint(21) NOT NULL COMMENT 'min value',`maxvalue` bigint(21) NOT NULL COMMENT 'max value',`start` bigint(21) NOT NULL COMMENT 'start value',`increment` bigint(21) NOT NULL COMMENT 'increment value',`cache` bigint(21) NOT NULL COMMENT 'cache size',`cycle` bigint(21) NOT NULL COMMENT 'cycle state',`round` bigint(21) NOT NULL COMMENT 'already how many round' ) ENGINE=InnoDB DEFAULT CHARSET=latin1由于SEQUENCE是通過(guò)真正的引擎表來(lái)保存的,所以SHOW COMMAND看到仍然是engine table。
3. QUERY STATEMENT Syntax
SELECT [NEXTVAL | CURRVAL | *] FROM schema.sequence_name; SELECT [NEXTVAL | CURRVAL | *] FOR schema.sequence_name;這里支持兩種訪問(wèn)方式,FROM和FOR:
- FROM clause: 兼容正常的SELECT查詢語(yǔ)句,返回的結(jié)果是基表的數(shù)據(jù),不迭代NEXTVAL。
- FOR clause:兼容SQL Server的方法,返回的結(jié)果是迭代后NEXTVAL的值。
4. 兼容性
因?yàn)橐嫒軲YSQLDUMP的備份方式,所以支持另外一種CREATE SEQUENCE方法,即:通過(guò)創(chuàng)建SEQUENCE表和INSERT一行初始記錄的方式, 比如:
CREATE SEQUENCE schema.sequence_name (`currval` bigint(21) NOT NULL COMMENT 'current value',`nextval` bigint(21) NOT NULL COMMENT 'next value',`minvalue` bigint(21) NOT NULL COMMENT 'min value',`maxvalue` bigint(21) NOT NULL COMMENT 'max value',`start` bigint(21) NOT NULL COMMENT 'start value',`increment` bigint(21) NOT NULL COMMENT 'increment value',`cache` bigint(21) NOT NULL COMMENT 'cache size',`cycle` bigint(21) NOT NULL COMMENT 'cycle state',`round` bigint(21) NOT NULL COMMENT 'already how many round' ) ENGINE=InnoDB DEFAULT CHARSET=latin1INSERT INTO schema.sequence_name VALUES(0,0,1,9223372036854775807,1,1,10000,1,0); COMMIT;但強(qiáng)烈建議使用native的CREATE SEQUENCE方法。
5. 語(yǔ)法限制
- Sequence不支持subquery和join
- FOR clause只支持sequence表,普通引擎表不支持
- 可以使用SHOW CREATE TABLE或者SHOW CREATE SEQUENCE來(lái)訪問(wèn)SEQUENCE結(jié)構(gòu),但不能使用SHOW CREATE SEQUENCE訪問(wèn)普通表
- 不支持CREATE TABLE的時(shí)候指定SEQUENCE引擎,sequence表只能通過(guò)CREATE SEQUENCE的語(yǔ)法來(lái)創(chuàng)建
High level architecture
1. Sequence initialization
Sequence對(duì)象的創(chuàng)建,會(huì)轉(zhuǎn)化成擁有固定[CURRVAL, NEXTVAL, MINVALUE, MAXVALUE, START, INCREMENT, CACHE, CYCLE, ROUND]這9個(gè)字段的引擎表,并根據(jù)CREATE SEQUENCE clause的定義,初始化了一條數(shù)據(jù),所以sequence對(duì)象實(shí)質(zhì)上是擁有一條記錄的存儲(chǔ)引擎表,SLAVE復(fù)制的BINLOG使用CREATE SEQUENCE ...語(yǔ)句生成的QUERY EVENT來(lái)完成。
2. Sequence interface
SEQUENCE handler實(shí)現(xiàn)了一部分的handler interface,并定義了兩個(gè)重要的屬性,SEQUENCE_SHARE和BASE_TABLE_FILE,SEQUENCE_SHARE保存著共享的sequence對(duì)象屬性和CACHE的值,NEXTVAL的值首先從cache中獲取,只有在cache使用完了,才會(huì)查詢基表。 BASE_TABLE_FILE是基表的handler,對(duì)持久化的數(shù)據(jù)的訪問(wèn)和修改,都通過(guò)BASE_TABLE_FILE handler進(jìn)行訪問(wèn)。
3. Sequence cache
Sequence對(duì)象的CACHE值保存在SEQUENCE_SHARE中,使用SEQUENCE_SHARE::MUTEX進(jìn)行保護(hù),所有對(duì)cache的訪問(wèn)是串行的。比如cache size是20,那么SEQUENCE_SHARE中只是保存一個(gè)cache_end值,當(dāng)訪問(wèn)的NEXTVAL到了cache_end,就會(huì)從基表中獲取下一個(gè)batch放到cache中。NEXTVAL根據(jù)INCREMENT BY設(shè)置的步長(zhǎng)進(jìn)行迭代。
4. Sequence update
當(dāng)cache用完了之后,會(huì)從基表中獲取下一個(gè)batch,這樣會(huì)更新基表中的記錄,查詢會(huì)轉(zhuǎn)化成更新語(yǔ)句,
其更新的主要步驟如下:
5. Autonomous transaction
因?yàn)閚extval不支持ROLLBACK重用,所以必須重啟一個(gè)自治事務(wù)來(lái)脫離事務(wù)上下文, 其步驟如下:
6. Sequence read only
因?yàn)镾EQUENCE的SELECT語(yǔ)句會(huì)轉(zhuǎn)換成UPDATE語(yǔ)句,所以SELECT NEXTVAL FOR s?statement須持有 MDL_SHARE_WRITE 和 GLOBAL MDL_INTENSIVE_EXCLUSIVE METADATA LOCK 進(jìn)行,以便在READ ONLY的時(shí)候,阻塞對(duì)sequence對(duì)象的訪問(wèn)。
7. Skip cache
這里指兩種CACHE:
- 一種是SEQUENCE的CACHE,可以使用SELECT NEXTVAL FORM Sequence_name來(lái)skip。
- 另外一種是QUERY CACHE,所有的SEQUENCE都設(shè)置了不支持QUERY CACHE,這樣避免由于QUERY CACHE導(dǎo)致NEXTVAL沒(méi)有迭代。
8. Sequence backup
由于SEQUENCE是通過(guò)真正的引擎表來(lái)保存的,所以類(lèi)似XtraBackup這樣的物理備份可以直接使用,而類(lèi)似于MYSQLDUMP這樣的邏輯備份,SEQUENCE會(huì)備份成CREATE SEQUENCE語(yǔ)句和INSERT語(yǔ)句的組合來(lái)完成。
Next Release
本次開(kāi)源了部分功能,下一次release將繼續(xù)開(kāi)源SEQUENCE的部分功能:
- 支持CURRVAL的訪問(wèn),CURRVAL表示當(dāng)前session的上一次的NEXTVAL訪問(wèn)的值。
- 兼容更多數(shù)據(jù)庫(kù)的訪問(wèn)方法,比如:
Usage Scenario
1. 更具有業(yè)務(wù)含義的主鍵設(shè)計(jì)?.
例如:[八位日期 + 四位USER ID + sequence_number]的流水業(yè)務(wù)單據(jù)號(hào)的設(shè)計(jì)格式,可以通過(guò)SELECT NEXTVAL FOR Sequence和應(yīng)用封裝的方式實(shí)現(xiàn),相比較無(wú)意義的id數(shù)字,這種格式會(huì)帶來(lái)幾個(gè)優(yōu)勢(shì):
- 保持和時(shí)間同步的有序性,有利于數(shù)據(jù)的歸檔,比如可以直接使用這種ID來(lái)進(jìn)行按日/月/年RANGE分區(qū), 無(wú)縫使用MySQL的partition特性
- 增加USER的id信息,可以作為天然的分庫(kù)分表邏輯位, 提升數(shù)據(jù)節(jié)點(diǎn)可擴(kuò)展性
- 保持?jǐn)?shù)字的有序性,保證InnoDB這種聚簇索引表的插入性能穩(wěn)定
業(yè)界目前采用的設(shè)計(jì)方法:
- Booking使用了AUTO_INCREMENT的方法, 先插入一個(gè)無(wú)業(yè)務(wù)含義的數(shù)字,然后使用last_insert_id()方法獲取ID值,最后在業(yè)務(wù)邏輯中使用這個(gè)ID值。 其劣勢(shì)就是必須先插入,并沒(méi)有辦法再修改這個(gè)無(wú)業(yè)務(wù)含義的id。
- Twitter采用了另外一種格式,[41 bits timestamp + 10 bits configured machine ID + 12 bits sequence number], sequence number的生成機(jī)制沒(méi)有透露,machine ID的的設(shè)計(jì),使用Zookeeper來(lái)管理的machine ID或者機(jī)器的MAC address。
- UUID的方法,這種方式生成了一個(gè)隨機(jī)的唯一值,嚴(yán)重影響了插入的性能,并且增大了索引大小,降低了命中率,沒(méi)有任何優(yōu)勢(shì)。
2. 分布式節(jié)點(diǎn)的唯一值設(shè)計(jì)
分布式SEQUENCE生成:
- 可以為每一個(gè)節(jié)點(diǎn)設(shè)計(jì)sequence,比如為每個(gè)節(jié)點(diǎn)設(shè)計(jì)不同的INCREMENT BY步長(zhǎng)來(lái)達(dá)到MySQL AUTO_INCREMENT中,設(shè)置auto_increment_increment和auto_increment_offset的效果,但相比較auto increment的全局配置,并且保存在my.cnf中的方法,SEQUENCE可以把這些配置當(dāng)做sequence對(duì)象的屬性持久化保存下來(lái),優(yōu)勢(shì)明顯。但不推薦使用這種方法來(lái)設(shè)計(jì)唯一值,會(huì)給運(yùn)維留下不少坑。
- 使用類(lèi)似twitter的方法,每一個(gè)節(jié)點(diǎn)上創(chuàng)建sequence,然后增加節(jié)點(diǎn)信息到sequence number中,生成唯一值。
集中式SEQUENCE生成:
- 對(duì)于分布式節(jié)點(diǎn)中的ID需求,使用獨(dú)立的集中式的sequence服務(wù)來(lái)生成,但如果要保證持續(xù)可用,sequence服務(wù)仍然需要設(shè)計(jì)成多節(jié)點(diǎn)的,比如Flickr的Ticket Servers設(shè)計(jì):
Sequence服務(wù)節(jié)點(diǎn)上創(chuàng)建Ticket表:
CREATE TABLE `Tickets64` (`id` bigint(20) unsigned NOT NULL auto_increment,`stub` char(1) NOT NULL default '',PRIMARY KEY (`id`),UNIQUE KEY `stub` (`stub`) ) ENGINE=MyISAM+-------------------+------+ | id | stub | +-------------------+------+ | 72157623227190423 | a | +-------------------+------+使用以下語(yǔ)句,生成ID值:
REPLACE INTO Tickets64 (stub) VALUES ('a'); SELECT LAST_INSERT_ID();因?yàn)镻HOTOS,COMMENTS,FAVORITES,TAGS都需要ID, 所以會(huì)建不同的ticket表來(lái)完成,為了保持持續(xù)可用,采用了:
TicketServer1: auto-increment-increment = 2 auto-increment-offset = 1TicketServer2: auto-increment-increment = 2 auto-increment-offset = 2 來(lái)保證高可用。
如果使用sequence對(duì)象,可以大大簡(jiǎn)化ID的獲取邏輯,并更加安全。
總結(jié)
以上是生活随笔為你收集整理的AliSQL开源Sequence Engine的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 申请Let's Encrypt的证书
- 下一篇: Mysql semi-sync VS g