技术干货丨 TDSQL for MySQL DDL执行框架
共 9699字,需浏览 20分钟
·
2024-06-28 12:00
引言
背景
本文介绍 TDSQL for MySQL 架构中 DDL 框架实现原理。我们首先需要了解两个专业术语:
假设一个集群由2个 CN 和3个 DN 组成,那么一条 DDL 语句需要在所有 CN 和 DN 上执行,如果其中某个节点执行失败或超时,需要一些机制来让整个集群的元数据能自动恢复到一致状态,以减少人工干预。
DDL 框架的主要工作也是在保证各类 DDL 能够正确执行,主要包含以下几种方式:
本文将通过对 TDSQL for MySQL DDL 框架实现描述,让读者对 DDL 框架正确性保障有一个大概了解。
实现原理
1
执行流程总览
当客户端向 CN 提交一个 DDL 后,CN 会通过如下流程来执行:
1. 根据当前上下文信息创建出一个 DDL Job,并将任务信息持久化在元数据 DB 上,当前会选择最后一个 DN 作为元数据节点。
2. 开始执行 DDL 状态机:
a. 对所有 DN 和 CN 都进行前置检查。不同的 DDL 的类型,所做的前置检查也会不一样。
b. 广播需要执行的 DDL 至所有 DN 和 CN。当执行出错时,会自动进行重试。重试多次失败后,针对支持回滚的 DDL 类型,会自动回滚任务。
c. 写入元数据,并标记该任务执行完成。
3. 返回客户端执行结果。
整体流程如下图(该集群由2个 CN 和3个 DN 组成):
2
前置检查
DDL 框架为了保证集群整体的执行表现与单机 MySQL 一致,在真正执行 DDL 前会进行各种必要的前置检查,以减轻执行出错的可能性。
我们以 rename tables 场景来举例说明,假设集群由1个 CN 和2个 DN 组成,DN 分别为 DN1 和 DN2。t1 和 t2 都为分布式表(数据分布在一个或多个 DN 上),t1 表只存在于 DN1 上,t2 表存在于 DN1 和 DN2 上。当执行 rename table t1 to t1_new, t2 to t2_new 时,如果 DN1 上已经存在了 t1_new 表,那么当执行该 DDL 时会有如下两种应对方式:
因此,上述示例需要执行前置检查。同时,并不是所有 rename tables 中的 new table 都需要检查存在性,比如包含中间表的情况, rename table t1 to tmp, t2 to t1, tmp to t2,其中 tmp 作为一个中间表不应该去 DN 上检查存在性,否则也会产生一些误判导致 DDL 无法执行。
前置检查中除了表存在性检查,还会包含表的一致性检查,即查询所有 CN 和 DN 保证它们在执行 DDL 前表结构是一致的。该检查主要用于 Alter Table 的场景,为了防止在已经不一致的表结构上,继续追加变更,导致不一致的情况加剧,给后续恢复造成困难。
目前表一致性检查会包含如下几种分类:
前置检查中还会去所有 DN 上尝试短暂获取并释放被操作表的 Exclusive Lock,以降低执行 DDL 阶段时被锁阻塞的可能性。
举例来说,假设一个 Alter Table DDL 任务需要在所有 DN 上执行,如果某个 DN 上刚好存在一个长事务,如果不进行锁检查,那么该任务执行会一直等待,而在等待期间内,执行成功的 DN 的表结构已经发生了变化,从而造成了集群整体的不一致。
通过上述示例,可以发现必要前置检查可以一定程度地降低集群不一致的情况。下表也列举了当前不同 DDL 类型会包含的前置检查类型:
\ 前置检查类型 DDL 类型 |
表存在性检查 |
表一致性检查 |
锁检查 |
Create Table |
✅(非 if not exists) |
❌ |
❌ |
Drop Table |
✅(非 if exists) |
❌ |
✅ |
Rename Table |
✅ |
❌ |
✅ |
Alter Table |
✅ |
✅ |
✅ |
3
容错处理
DDL 执行阶段会在所有 DN 并行执行,待 DN 全部执行成功后,再在所有 CN 并行执行。不难发现,这个过程中很容易出现一些节点执行失败,另外一些节点执行成功的情况。举例来说,DDL 执行 DN 阶段某个 DN 突然重启导致连接断开,这时则需要进行重试来恢复执行。
对于重试的策略,我们采用了尽可能重试的策略,来尽可能保证执行成功。以并行执行 DN 举例,会包含如下策略:
当遇到无法重试的错误,或重试多次失败后,DDL 框架会对支持的 DDL 类型进行自动回滚。比如:
4
任务接管
CN 本身是一个无状态的计算节点,集群中会存在多个 CN 的情况,并且每个 CN 都可以执行 DDL 任务。任务接管主要讨论的一个场景是,当 CN 执行 DDL 任务期间发生故障时,如何将任务继续执行下去。
DDL 框架通过一些机制和后台线程来减轻该问题:
下图是任务接管和执行的大致流程:
周边管理命令
除了上文提到的 DDL 框架自身的正确性保障机制,真实使用场景中还需要一些周边命令,来增强任务观测性和补偿异常任务,以减轻人为干预成本。
下面会依次介绍目前支持的 DDL 管理命令。
1
SHOW DDL
该命令用于展示当前集群中正在执行或已经执行结束的所有 DDL 任务。主要使用场景如下:
● 观察任务当前执行状态,是否成功或失败、执行的耗时、执行任务的 CN 信息等。
下面简单列举了使用用例:
-- 只展示当前正在执行的任务
SHOW DDL;
-- 只展示任务 ID 为8的任务
SHOW DDL 8;
-- 展示最近10个任务
SHOW FULL DDL LIMIT 10;
-- 筛选表名为 test 的正在执行的任务
SHOW DDL WHERE `table_name`='test';
-- 筛选表名为 test 的所有任务
SHOW FULL DDL WHERE `table_name`='test';
-- 筛选所有的任务,并用 '%err%' 去模糊匹配任务信息
SHOW FULL DDL LIKE '%err%';
2
KILL DDL
该命令用于强制停止当前正在执行的任务。主要使用场景如下:
● 当前正在执行的 DDL 任务耗时过长,影响正常 DML,需要强制停止并断开与所有 DN 的连接。
● 误提交了某个 DDL 任务,需要强制停止。
下面简单列举了使用用例:
-- 1. 通过 SHOW DDL 获取需要强制停止的任务ID,假设任务ID为9
SHOW DDL;
-- 2. 执行 KILL DDL
KILL DDL 9;
-- 3. 通过 SHOW DDL 观察任务被停止状态
SHOW DDL 9;
3
REPEAT DDL
该命令用于重新执行已经完成并执行失败的 DDL 任务。并且会检查所操作的表不能存在已经执行成功的 DDL 任务。主要使用场景如下:
下面简单列举了使用用例:
-- 1. 通过 SHOW DDL 获取需要强制停止的任务ID,假设任务ID为9
SHOW FULL DDL WHERE `error_code` !=0;
-- 2. 执行 REPEAT DDL
ALTER REPEAT 9;
-- 3. 通过 SHOW DDL 观察任务重新状态
SHOW DDL 9;
4
计算节点本地对象的DDL
-
PROCEDURE -
SPFUNCTION -
FUNCTION -
TRIGGER -
EVENT -
VIEW
这些类型的 DDL 也会通过 DDL 框架去执行,因此周边的管理命令也同样适用于它们。但是,它们的执行流程不同于广播 SQL 的方式,而是通过异步通知并同步等待的方式让所有 CN 节点来执行。
具体执行流程如下:
1.向元数据 DB 写入 DDL 任务信息,并异步通知所有 CN。
2.同步等待所有 CN 执行结果。
3.通过 SHOW CREATE 语句获取 DDL 定义语句,并写入 snapshot 表中,用于后期增量或全量同步。
除了正常的执行流程以为,我们还加入了如下正确性保证:
●相邻 CN 下线后,执行阶段能够正确感知并将它剔除执行结果判断。
●每个 CN 都会包含一个系统表,用于持久化目前已经执行的 DDL 的版本号,保证后期同步时的幂等性和数据完备性。
同步模块
该模块属于元数据同步模块,但与 DDL 框架密不可分,因为 CN 作为一个无状态计算节点,需要再启动或网络隔离后,能够快速追上当前集群最新元数据信息。下面会分别介绍 CN 在初始启动和网络隔离恢复后的同步行为是如何保证正确性的。
1
初始启动
同步阶段会包含两类不同的同步流程,第一类为常规 DDL 同步流程,第二类为计算节点本地对象的 DDL 同步流程。这两类同步流程都需要持久化一个最大版本信息,记录当前已经应用过的版本,下文会将他们称为 applied_version。
2
网络隔离
CN 可能会处于网络隔离环境中后恢复,这是需要依赖后台线程定期扫描机制,来察觉当前 applied_version 是否已经落后再进行同步。
同步流程与初始启动流程一致。但区别在于,相比启动阶段先会执行常规 DDL 同步流程,再执行计算节点本地对象的 DDL 同步流程来说,后台同步线程是并行执行的,但常规 DDL 与 计算节点本地对象的 DDL 是有明显的偏序关系。比如,CREATE VIEW V1 AS SELECT * FROM T1; 需要依赖 CREATE TABLE T1 已经执行成功。如果同步计算节点本地对象的 DDL 的线程先执行任务,就会导致执行报错。
针对这种存在偏序关系的同步问题,有多种应对方案,比如:计算节点本地对象的 DDL 同步流程需要更完备的前置检查与重试机制来修复同步乱序问题;通过全局序列号给两种类型的 DDL 同步事件定序来实现全序关系。受限于篇幅,该部分也会在未来分享。
总结
本文详细阐述了 DDL 框架在正确性保障方面的任务容错能力和任务管理能力,未来也会分享更多复杂点:
● 更全面的自动回滚场景:针对 ALTER TABLE 下的具体场景,通过进一步细分,来实现某些异常下的自动回滚的能力,以整体提高易用性。比如 CREATE INDEX 场景。
● 更全面的隔离性:DDL 框架执行或同步阶段无法避免并发的 DML 访问到一些中间状态,但可以通过多版本元数据,来提高与 DML 之间的隔离性。
﹀
﹀
﹀
报名开启|7月5日,WAIC腾讯论坛邀您一起智创未来
TDSQL for PG 优化器Join Reordering原理分析
云原生数据库 TDSQL-C 高可用,一键实现多可用区部署