TiDB实现Mysql分库分表合并数据同步
一、迁移工具TiDB Data Migration
TiDB Data Migration,简称DM,一体化数据迁移任务管理平台,支持从MySQL或MariaDB到TiDB的全量数据迁移和增量数据复制。
DM 以 SQL 语句的形式将数据迁移到 TiDB 中,因此各个版本的 DM 都分别兼容所有版本的 TiDB。在生产环境中,推荐使用 DM 的最新已发布版本。
DM由主要由DM-master、DM-Worker和dmtcl组成,介绍参考官方文档。
DM-master
DM-master 负责管理和调度数据迁移任务的各项操作。
- 保存 DM 集群的拓扑信息
- 监控 DM-worker 进程的运行状态
- 监控数据迁移任务的运行状态
- 提供数据迁移任务管理的统一入口
- 协调分库分表场景下各个实例分表的 DDL 迁移
DM-worker
DM-worker 负责执行具体的数据迁移任务。
- 将 binlog 数据持久化保存在本地
- 保存数据迁移子任务的配置信息
- 编排数据迁移子任务的运行
- 监控数据迁移子任务的运行状态
DM-worker 启动后,会自动迁移上游 binlog 至本地配置目录(如果使用 DM-Ansible 部署 DM 集群,默认的迁移目录为 <deploy_dir>/relay_log
)。
dmctl
dmctl 是用来控制 DM 集群的命令行工具。
- 创建、更新或删除数据迁移任务
- 查看数据迁移任务状态
- 处理数据迁移任务错误
- 校验数据迁移任务配置的正确性
迁移过程中碰到一些问题如分库分表数据冲突、不支持DROP DATABASE/TABLE
二、分库同步
在上图的例子中,分表的合库合表过程简化成了上游只有两个 MySQL 实例,每个实例内只有一个表。假设在数据迁移开始时,将两个分表的表结构版本记为 schema V1,将 DDL 语句执行完后的表结构版本记为 schema V2。
现在,假设数据迁移过程中,DM-worker 内的 binlog 复制单元(sync)从两个上游分表收到的 binlog 数据有如下时序:
- 开始迁移时,sync 从两个分表收到的都是 schema V1 版本的 DML 语句。
- 在 t1 时刻,sync 收到实例 1 上分表的 DDL 语句。
- 从 t2 时刻开始,sync 从实例 1 收到的是 schema V2 版本的 DML 语句;但从实例 2 收到的仍是 schema V1 版本的 DML 语句。
- 在 t3 时刻,sync 收到实例 2 上分表的 DDL 语句。
- 从 t4 时刻开始,sync 从实例 2 收到的也是 schema V2 版本的 DML 语句。
假设在数据迁移过程中,不对分表的 DDL 语句进行额外处理。当实例 1 的 DDL 语句迁移到下游后,下游的表结构会变更成为 schema V2 版本。但在 t2 到 t3 这段时间内,sync 从实例 2 上收到的仍是 schema V1 版本的 DML 语句。当尝试把这些 schema V1 版本的 DML 语句迁移到下游时,就会由于 DML 语句与表结构的不一致而发生错误,从而无法正确迁移数据。
实现原理
在这个例子中,DM-worker-1 负责迁移来自 MySQL 实例 1 的数据,DM-worker-2 负责迁移来自 MySQL 实例 2 的数据,DM-master 负责协调多个 DM-worker 间的 DDL 迁移。
从 DM-worker-1 收到 DDL 语句开始,简化后的 DDL 迁移流程为:
- 在 t1 时刻,DM-worker-1 收到来自 MySQL 实例 1 的 DDL 语句,自身暂停该 DDL 语句对应任务的 DDL 及 DML 数据迁移,并将 DDL 相关信息发送给 DM-master。
- DM-master 根据收到的 DDL 信息判断得知需要协调该 DDL 语句的迁移,于是为该 DDL 语句创建一个锁,并将 DDL 锁信息发回给 DM-worker-1,同时将 DM-worker-1 标记为这个锁的 owner。
- DM-worker-2 继续进行 DML 语句的迁移,直到在 t3 时刻收到来自 MySQL 实例 2 的 DDL 语句,自身暂停该 DDL 语句对应任务的数据迁移,并将 DDL 相关信息发送给 DM-master。
- DM-master 根据收到的 DDL 信息判断得知该 DDL 语句对应的锁信息已经存在,于是直接将对应锁信息发回给 DM-worker-2。
- 根据任务启动时的配置信息、上游 MySQL 实例分表信息、部署拓扑信息等,DM-master 判断得知自身已经收到了来自待合表的所有上游分表的 DDL 语句,于是请求 DDL 锁的 owner(DM-worker-1)向下游迁移执行该 DDL。
- DM-worker-1 根据第二步收到的 DDL 锁信息验证 DDL 语句执行请求;向下游执行 DDL,并将执行结果反馈给 DM-master;若 DDL 语句执行成功,则自身开始继续迁移后续的(从 t2 时刻对应的 binlog 开始的)DML 语句。
- DM-master 收到来自 owner 执行 DDL 语句成功的响应,于是请求在等待该 DDL 锁的所有其他 DM-worker(DM-worker-2)忽略该 DDL 语句,直接继续迁移后续的(从 t4 时刻对应的 binlog 开始的)DML 语句。
根据上面的流程,可以归纳出 DM 协调多个 DM-worker 间 sharding DDL 迁移的特点:
- 根据任务配置与 DM 集群部署拓扑信息,DM-master 内部也会建立一个逻辑 sharding group 来协调 DDL 迁移,group 中的成员为负责处理该迁移任务拆解后的各子任务的 DM-worker。
- 各 DM-worker 从 binlog event 中收到 DDL 语句后,会将 DDL 信息发送给 DM-master。
- DM-master 根据来自 DM-worker 的 DDL 信息及 sharding group 信息创建或更新 DDL 锁。
- 如果 sharding group 的所有成员都收到了某一条相同的 DDL 语句,则表明上游分表在该 DDL 执行前的 DML 语句都已经迁移完成,此时可以执行该 DDL 语句,并继续后续的 DML 迁移。
- 上游所有分表的 DDL 在经过 table router 转换后需要保持一致,因此仅需 DDL 锁的 owner 执行一次该 DDL 语句即可,其他 DM-worker 可直接忽略对应的 DDL 语句。
在上面的示例中,每个 DM-worker 对应的上游 MySQL 实例中只有一个待合并的分表。但在实际场景下,一个 MySQL 实例可能有多个分库内的多个分表需要进行合并,这种情况下,sharding DDL 的协调迁移过程将更加复杂。
三、分表同步
在这个例子中,由于数据来自同一个 MySQL 实例,因此所有数据都是从同一个 binlog 流中获得,时序如下:
- 开始迁移时,DM-worker 内的 sync 从两个分表收到的数据都是 schema V1 版本的 DML 语句。
- 在 t1 时刻,sync 收到 table_1 分表的 DDL 语句。
- 从 t2 到 t3 时刻,sync 收到的数据同时包含 table_1 的 DML 语句(schema V2 版本)及 table_2 的 DML 语句(schema V1 版本)。
- 在 t3 时刻,sync 收到 table_2 分表的 DDL 语句。
- 从 t4 时刻开始,sync 从两个分表收到的数据都是 schema V2 版本的 DML 语句。
假设在数据迁移过程中,不对分表的 DDL 语句进行额外处理。当 table_1 的 DDL 语句迁移到下游从而变更下游表结构后,table_2 的 DML 语句(schema V1 版本)将无法正常迁移。因此,在单个 DM-worker 内部,我们也构造了与 DM-master 内类似的逻辑 sharding group,但 group 的成员是同一个上游 MySQL 实例的不同分表。
DM-worker 内协调处理 sharding group 的迁移与 DM-master 处理 DM-worker 之间的迁移不完全一致,主要原因包括:
- 当 DM-worker 收到 table_1 分表的 DDL 语句时,迁移不能暂停,需要继续解析 binlog 才能获得后续 table_2 分表的 DDL 语句,即需要从 t2 时刻继续解析直到 t3 时刻。
- 在继续解析 t2 到 t3 时刻的 binlog 的过程中,table_1 分表的 DML 语句(schema V2 版本)不能向下游迁移;但当 sharding DDL 迁移并执行成功后,这些 DML 语句则需要迁移到下游。
DM-worker 内部 sharding DDL 迁移的简化流程为:
- 在 t1 时刻,DM-worker 收到 table_1 的 DDL 语句,并记录 DDL 信息及此时的 binlog 位置点信息。
- DM-worker 继续向前解析 t2 到 t3 时刻的 binlog。
- 对于 table_1 的 DML 语句(schema V2 版本),忽略;对于 table_2 的 DML 语句(schema V1 版本),正常迁移到下游。
- 在 t3 时刻,DM-worker 收到 table_2 的 DDL 语句,并记录 DDL 信息及此时的 binlog 位置点信息。
- 根据迁移任务配置信息、上游库表信息等,DM-worker 判断得知该 MySQL 实例上所有分表的 DDL 语句都已收到;于是将该 DDL 语句迁移到下游执行并变更下游表结构。
- DM-worker 设置 binlog 流的新解析起始位置点为第一步时保存的位置点。
- DM-worker 重新开始解析从 t2 到 t3 时刻的 binlog。
- 对于 table_1 的 DML 语句(schema V2 版本),正常迁移到下游;对于 table_2 的 DML 语句(schema V1 版本),忽略。
- 解析到达第四步时保存的 binlog 位置点,可得知在第三步时被忽略的所有 DML 语句都已经重新迁移到下游。
- DM-worker 继续从 t4 时刻对应的 binlog 位置点开始正常迁移。
综上可知,DM 在处理 sharding DDL 迁移时,主要通过两级 sharding group 来进行协调控制,简化的流程为:
- 各 DM-worker 独立地协调对应上游 MySQL 实例内多个分表组成的 sharding group 的 DDL 迁移。
- 当 DM-worker 收到所有分表的 DDL 语句时,向 DM-master 发送 DDL 相关信息。
- DM-master 根据 DM-worker 发来的 DDL 信息,协调由各 DM-worker 组成的 sharing group 的 DDL 迁移。
- 当 DM-master 收到所有 DM-worker 的 DDL 信息时,请求 DDL 锁的 owner(某个 DM-worker) 执行该 DDL 语句。
- DDL 锁的 owner 执行 DDL 语句,并将结果反馈给 DM-master;自身开始重新迁移在内部协调 DDL 迁移过程中被忽略的 DML 语句。
- 当 DM-master 收到 owner 执行 DDL 成功的消息后,请求其他所有 DM-worker 继续开始迁移。
- 其他所有 DM-worker 各自开始重新迁移在内部协调 DDL 迁移过程中被忽略的 DML 语句。
- 在完成被忽略的 DML 语句的重新迁移后,所有 DM-worker 继续正常迁移。