技術(shù)干貨丨 TDSQL for MySQL DDL執(zhí)行框架
共 9699字,需瀏覽 20分鐘
·
2024-06-28 12:00
引言
背景
本文介紹 TDSQL for MySQL 架構(gòu)中 DDL 框架實(shí)現(xiàn)原理。我們首先需要了解兩個(gè)專業(yè)術(shù)語(yǔ):
假設(shè)一個(gè)集群由2個(gè) CN 和3個(gè) DN 組成,那么一條 DDL 語(yǔ)句需要在所有 CN 和 DN 上執(zhí)行,如果其中某個(gè)節(jié)點(diǎn)執(zhí)行失敗或超時(shí),需要一些機(jī)制來(lái)讓整個(gè)集群的元數(shù)據(jù)能自動(dòng)恢復(fù)到一致?tīng)顟B(tài),以減少人工干預(yù)。
DDL 框架的主要工作也是在保證各類 DDL 能夠正確執(zhí)行,主要包含以下幾種方式:
本文將通過(guò)對(duì) TDSQL for MySQL DDL 框架實(shí)現(xiàn)描述,讓讀者對(duì) DDL 框架正確性保障有一個(gè)大概了解。
實(shí)現(xiàn)原理
1
執(zhí)行流程總覽
當(dāng)客戶端向 CN 提交一個(gè) DDL 后,CN 會(huì)通過(guò)如下流程來(lái)執(zhí)行:
1. 根據(jù)當(dāng)前上下文信息創(chuàng)建出一個(gè) DDL Job,并將任務(wù)信息持久化在元數(shù)據(jù) DB 上,當(dāng)前會(huì)選擇最后一個(gè) DN 作為元數(shù)據(jù)節(jié)點(diǎn)。
2. 開(kāi)始執(zhí)行 DDL 狀態(tài)機(jī):
a. 對(duì)所有 DN 和 CN 都進(jìn)行前置檢查。不同的 DDL 的類型,所做的前置檢查也會(huì)不一樣。
b. 廣播需要執(zhí)行的 DDL 至所有 DN 和 CN。當(dāng)執(zhí)行出錯(cuò)時(shí),會(huì)自動(dòng)進(jìn)行重試。重試多次失敗后,針對(duì)支持回滾的 DDL 類型,會(huì)自動(dòng)回滾任務(wù)。
c. 寫(xiě)入元數(shù)據(jù),并標(biāo)記該任務(wù)執(zhí)行完成。
3. 返回客戶端執(zhí)行結(jié)果。
整體流程如下圖(該集群由2個(gè) CN 和3個(gè) DN 組成):
2
前置檢查
DDL 框架為了保證集群整體的執(zhí)行表現(xiàn)與單機(jī) MySQL 一致,在真正執(zhí)行 DDL 前會(huì)進(jìn)行各種必要的前置檢查,以減輕執(zhí)行出錯(cuò)的可能性。
我們以 rename tables 場(chǎng)景來(lái)舉例說(shuō)明,假設(shè)集群由1個(gè) CN 和2個(gè) DN 組成,DN 分別為 DN1 和 DN2。t1 和 t2 都為分布式表(數(shù)據(jù)分布在一個(gè)或多個(gè) DN 上),t1 表只存在于 DN1 上,t2 表存在于 DN1 和 DN2 上。當(dāng)執(zhí)行 rename table t1 to t1_new, t2 to t2_new 時(shí),如果 DN1 上已經(jīng)存在了 t1_new 表,那么當(dāng)執(zhí)行該 DDL 時(shí)會(huì)有如下兩種應(yīng)對(duì)方式:
因此,上述示例需要執(zhí)行前置檢查。同時(shí),并不是所有 rename tables 中的 new table 都需要檢查存在性,比如包含中間表的情況, rename table t1 to tmp, t2 to t1, tmp to t2,其中 tmp 作為一個(gè)中間表不應(yīng)該去 DN 上檢查存在性,否則也會(huì)產(chǎn)生一些誤判導(dǎo)致 DDL 無(wú)法執(zhí)行。
前置檢查中除了表存在性檢查,還會(huì)包含表的一致性檢查,即查詢所有 CN 和 DN 保證它們?cè)趫?zhí)行 DDL 前表結(jié)構(gòu)是一致的。該檢查主要用于 Alter Table 的場(chǎng)景,為了防止在已經(jīng)不一致的表結(jié)構(gòu)上,繼續(xù)追加變更,導(dǎo)致不一致的情況加劇,給后續(xù)恢復(fù)造成困難。
目前表一致性檢查會(huì)包含如下幾種分類:
前置檢查中還會(huì)去所有 DN 上嘗試短暫獲取并釋放被操作表的 Exclusive Lock,以降低執(zhí)行 DDL 階段時(shí)被鎖阻塞的可能性。
舉例來(lái)說(shuō),假設(shè)一個(gè) Alter Table DDL 任務(wù)需要在所有 DN 上執(zhí)行,如果某個(gè) DN 上剛好存在一個(gè)長(zhǎng)事務(wù),如果不進(jìn)行鎖檢查,那么該任務(wù)執(zhí)行會(huì)一直等待,而在等待期間內(nèi),執(zhí)行成功的 DN 的表結(jié)構(gòu)已經(jīng)發(fā)生了變化,從而造成了集群整體的不一致。
通過(guò)上述示例,可以發(fā)現(xiàn)必要前置檢查可以一定程度地降低集群不一致的情況。下表也列舉了當(dāng)前不同 DDL 類型會(huì)包含的前置檢查類型:
\ 前置檢查類型 DDL 類型 |
表存在性檢查 |
表一致性檢查 |
鎖檢查 |
Create Table |
?(非 if not exists) |
? |
? |
Drop Table |
?(非 if exists) |
? |
? |
Rename Table |
? |
? |
? |
Alter Table |
? |
? |
? |
3
容錯(cuò)處理
DDL 執(zhí)行階段會(huì)在所有 DN 并行執(zhí)行,待 DN 全部執(zhí)行成功后,再在所有 CN 并行執(zhí)行。不難發(fā)現(xiàn),這個(gè)過(guò)程中很容易出現(xiàn)一些節(jié)點(diǎn)執(zhí)行失敗,另外一些節(jié)點(diǎn)執(zhí)行成功的情況。舉例來(lái)說(shuō),DDL 執(zhí)行 DN 階段某個(gè) DN 突然重啟導(dǎo)致連接斷開(kāi),這時(shí)則需要進(jìn)行重試來(lái)恢復(fù)執(zhí)行。
對(duì)于重試的策略,我們采用了盡可能重試的策略,來(lái)盡可能保證執(zhí)行成功。以并行執(zhí)行 DN 舉例,會(huì)包含如下策略:
當(dāng)遇到無(wú)法重試的錯(cuò)誤,或重試多次失敗后,DDL 框架會(huì)對(duì)支持的 DDL 類型進(jìn)行自動(dòng)回滾。比如:
4
任務(wù)接管
CN 本身是一個(gè)無(wú)狀態(tài)的計(jì)算節(jié)點(diǎn),集群中會(huì)存在多個(gè) CN 的情況,并且每個(gè) CN 都可以執(zhí)行 DDL 任務(wù)。任務(wù)接管主要討論的一個(gè)場(chǎng)景是,當(dāng) CN 執(zhí)行 DDL 任務(wù)期間發(fā)生故障時(shí),如何將任務(wù)繼續(xù)執(zhí)行下去。
DDL 框架通過(guò)一些機(jī)制和后臺(tái)線程來(lái)減輕該問(wèn)題:
下圖是任務(wù)接管和執(zhí)行的大致流程:
周邊管理命令
除了上文提到的 DDL 框架自身的正確性保障機(jī)制,真實(shí)使用場(chǎng)景中還需要一些周邊命令,來(lái)增強(qiáng)任務(wù)觀測(cè)性和補(bǔ)償異常任務(wù),以減輕人為干預(yù)成本。
下面會(huì)依次介紹目前支持的 DDL 管理命令。
1
SHOW DDL
該命令用于展示當(dāng)前集群中正在執(zhí)行或已經(jīng)執(zhí)行結(jié)束的所有 DDL 任務(wù)。主要使用場(chǎng)景如下:
● 觀察任務(wù)當(dāng)前執(zhí)行狀態(tài),是否成功或失敗、執(zhí)行的耗時(shí)、執(zhí)行任務(wù)的 CN 信息等。
下面簡(jiǎn)單列舉了使用用例:
-- 只展示當(dāng)前正在執(zhí)行的任務(wù)
SHOW DDL;
-- 只展示任務(wù) ID 為8的任務(wù)
SHOW DDL 8;
-- 展示最近10個(gè)任務(wù)
SHOW FULL DDL LIMIT 10;
-- 篩選表名為 test 的正在執(zhí)行的任務(wù)
SHOW DDL WHERE `table_name`='test';
-- 篩選表名為 test 的所有任務(wù)
SHOW FULL DDL WHERE `table_name`='test';
-- 篩選所有的任務(wù),并用 '%err%' 去模糊匹配任務(wù)信息
SHOW FULL DDL LIKE '%err%';
2
KILL DDL
該命令用于強(qiáng)制停止當(dāng)前正在執(zhí)行的任務(wù)。主要使用場(chǎng)景如下:
● 當(dāng)前正在執(zhí)行的 DDL 任務(wù)耗時(shí)過(guò)長(zhǎng),影響正常 DML,需要強(qiáng)制停止并斷開(kāi)與所有 DN 的連接。
● 誤提交了某個(gè) DDL 任務(wù),需要強(qiáng)制停止。
下面簡(jiǎn)單列舉了使用用例:
-- 1. 通過(guò) SHOW DDL 獲取需要強(qiáng)制停止的任務(wù)ID,假設(shè)任務(wù)ID為9
SHOW DDL;
-- 2. 執(zhí)行 KILL DDL
KILL DDL 9;
-- 3. 通過(guò) SHOW DDL 觀察任務(wù)被停止?fàn)顟B(tài)
SHOW DDL 9;
3
REPEAT DDL
該命令用于重新執(zhí)行已經(jīng)完成并執(zhí)行失敗的 DDL 任務(wù)。并且會(huì)檢查所操作的表不能存在已經(jīng)執(zhí)行成功的 DDL 任務(wù)。主要使用場(chǎng)景如下:
下面簡(jiǎn)單列舉了使用用例:
-- 1. 通過(guò) SHOW DDL 獲取需要強(qiáng)制停止的任務(wù)ID,假設(shè)任務(wù)ID為9
SHOW FULL DDL WHERE `error_code` !=0;
-- 2. 執(zhí)行 REPEAT DDL
ALTER REPEAT 9;
-- 3. 通過(guò) SHOW DDL 觀察任務(wù)重新?tīng)顟B(tài)
SHOW DDL 9;
4
計(jì)算節(jié)點(diǎn)本地對(duì)象的DDL
-
PROCEDURE -
SPFUNCTION -
FUNCTION -
TRIGGER -
EVENT -
VIEW
這些類型的 DDL 也會(huì)通過(guò) DDL 框架去執(zhí)行,因此周邊的管理命令也同樣適用于它們。但是,它們的執(zhí)行流程不同于廣播 SQL 的方式,而是通過(guò)異步通知并同步等待的方式讓所有 CN 節(jié)點(diǎn)來(lái)執(zhí)行。
具體執(zhí)行流程如下:
1.向元數(shù)據(jù) DB 寫(xiě)入 DDL 任務(wù)信息,并異步通知所有 CN。
2.同步等待所有 CN 執(zhí)行結(jié)果。
3.通過(guò) SHOW CREATE 語(yǔ)句獲取 DDL 定義語(yǔ)句,并寫(xiě)入 snapshot 表中,用于后期增量或全量同步。
除了正常的執(zhí)行流程以為,我們還加入了如下正確性保證:
●相鄰 CN 下線后,執(zhí)行階段能夠正確感知并將它剔除執(zhí)行結(jié)果判斷。
●每個(gè) CN 都會(huì)包含一個(gè)系統(tǒng)表,用于持久化目前已經(jīng)執(zhí)行的 DDL 的版本號(hào),保證后期同步時(shí)的冪等性和數(shù)據(jù)完備性。
同步模塊
該模塊屬于元數(shù)據(jù)同步模塊,但與 DDL 框架密不可分,因?yàn)?CN 作為一個(gè)無(wú)狀態(tài)計(jì)算節(jié)點(diǎn),需要再啟動(dòng)或網(wǎng)絡(luò)隔離后,能夠快速追上當(dāng)前集群最新元數(shù)據(jù)信息。下面會(huì)分別介紹 CN 在初始啟動(dòng)和網(wǎng)絡(luò)隔離恢復(fù)后的同步行為是如何保證正確性的。
1
初始啟動(dòng)
同步階段會(huì)包含兩類不同的同步流程,第一類為常規(guī) DDL 同步流程,第二類為計(jì)算節(jié)點(diǎn)本地對(duì)象的 DDL 同步流程。這兩類同步流程都需要持久化一個(gè)最大版本信息,記錄當(dāng)前已經(jīng)應(yīng)用過(guò)的版本,下文會(huì)將他們稱為 applied_version。
2
網(wǎng)絡(luò)隔離
CN 可能會(huì)處于網(wǎng)絡(luò)隔離環(huán)境中后恢復(fù),這是需要依賴后臺(tái)線程定期掃描機(jī)制,來(lái)察覺(jué)當(dāng)前 applied_version 是否已經(jīng)落后再進(jìn)行同步。
同步流程與初始啟動(dòng)流程一致。但區(qū)別在于,相比啟動(dòng)階段先會(huì)執(zhí)行常規(guī) DDL 同步流程,再執(zhí)行計(jì)算節(jié)點(diǎn)本地對(duì)象的 DDL 同步流程來(lái)說(shuō),后臺(tái)同步線程是并行執(zhí)行的,但常規(guī) DDL 與 計(jì)算節(jié)點(diǎn)本地對(duì)象的 DDL 是有明顯的偏序關(guān)系。比如,CREATE VIEW V1 AS SELECT * FROM T1; 需要依賴 CREATE TABLE T1 已經(jīng)執(zhí)行成功。如果同步計(jì)算節(jié)點(diǎn)本地對(duì)象的 DDL 的線程先執(zhí)行任務(wù),就會(huì)導(dǎo)致執(zhí)行報(bào)錯(cuò)。
針對(duì)這種存在偏序關(guān)系的同步問(wèn)題,有多種應(yīng)對(duì)方案,比如:計(jì)算節(jié)點(diǎn)本地對(duì)象的 DDL 同步流程需要更完備的前置檢查與重試機(jī)制來(lái)修復(fù)同步亂序問(wèn)題;通過(guò)全局序列號(hào)給兩種類型的 DDL 同步事件定序來(lái)實(shí)現(xiàn)全序關(guān)系。受限于篇幅,該部分也會(huì)在未來(lái)分享。
總結(jié)
本文詳細(xì)闡述了 DDL 框架在正確性保障方面的任務(wù)容錯(cuò)能力和任務(wù)管理能力,未來(lái)也會(huì)分享更多復(fù)雜點(diǎn):
● 更全面的自動(dòng)回滾場(chǎng)景:針對(duì) ALTER TABLE 下的具體場(chǎng)景,通過(guò)進(jìn)一步細(xì)分,來(lái)實(shí)現(xiàn)某些異常下的自動(dòng)回滾的能力,以整體提高易用性。比如 CREATE INDEX 場(chǎng)景。
● 更全面的隔離性:DDL 框架執(zhí)行或同步階段無(wú)法避免并發(fā)的 DML 訪問(wèn)到一些中間狀態(tài),但可以通過(guò)多版本元數(shù)據(jù),來(lái)提高與 DML 之間的隔離性。
﹀
﹀
﹀
報(bào)名開(kāi)啟|7月5日,WAIC騰訊論壇邀您一起智創(chuàng)未來(lái)
TDSQL for PG 優(yōu)化器Join Reordering原理分析
云原生數(shù)據(jù)庫(kù) TDSQL-C 高可用,一鍵實(shí)現(xiàn)多可用區(qū)部署
