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