一、DM 架构解析
1.1 组件角色
DM 集群由三个核心组件构成:
DM 架构全景图:
┌──────────────────┐
│ DM-master │ 调度中枢
│ (Raft 高可用) │
│ 端口: 8261 │
└────────┬─────────┘
│
┌────┴────┐
v v
┌───────┐ ┌───────┐
│DM-W1 │ │DM-W2 │ Worker,执行实际迁移
│端口:8262│ │端口:8262│
└───┬───┘ └───┬───┘
│ │
v v
MySQL-A MySQL-B TiDB (下游)
(上游) (上游) 10.0.1.14:4000
| 组件 | 角色 | 说明 |
|---|---|---|
| DM-master | 集群调度 | 基于 etcd 实现高可用,管理 Worker 分配和任务调度 |
| DM-worker | 任务执行 | 运行在 MySQL 所在机器,负责拉取 binlog 和执行迁移 |
| dmctl | 命令行工具 | 用户通过 dmctl 与 DM-master 交互,管理任务 |
1.2 数据流转过程
DM 数据迁移三阶段:
阶段 1: 全量迁移 (Loader)
┌────────┐ ┌────────┐ ┌────────┐
│ MySQL │ ─dump─>│ 磁盘文件│ ─load─>│ TiDB │
│ │ (SQL) │ (.sql) │ │ │
└────────┘ └────────┘ └────────┘
阶段 2: 增量同步 (Syncer)
┌────────┐ ┌────────┐ ┌────────┐
│ MySQL │ ─binlog─>│ DM-W │ ─relay─>│ TiDB │
│ │ (binary) │ │ (SQL) │ │
└────────┘ └────────┘ └────────┘
阶段 3: 持续同步 (可选)
┌────────┐ 持续监控 ┌────────┐ 持续写入 ┌────────┐
│ MySQL │ ──────────> │ DM-W │ ──────────> │ TiDB │
│ (业务写入)│ binlog │ │ 重放 │ │
└────────┘ └────────┘ └────────┘
二、DM 集群部署
2.1 硬件需求
| 组件 | CPU | 内存 | 磁盘 | 说明 |
|---|---|---|---|---|
| DM-master | 4 核 | 8GB | SSD 100GB | etcd 对磁盘 I/O 敏感 |
| DM-worker | 8 核 | 16GB+ | SSD 200GB+ | 需要存储 relay log |
| 监控 | 4 核 | 8GB | 100GB | Prometheus + Grafana |
2.2 使用 TiUP 部署
# dm-topology.yaml
---
# 全局配置
global:
user: "tidb"
ssh_port: 22
deploy_dir: "/dm-deploy"
data_dir: "/dm-data"
# DM-master (推荐 3 节点高可用)
master_servers:
- host: 10.0.1.50
port: 8261
peer_port: 8291
- host: 10.0.1.51
port: 8261
peer_port: 8291
- host: 10.0.1.52
port: 8261
peer_port: 8291
# DM-worker (每个 MySQL 源对应一个 worker)
worker_servers:
- host: 10.0.2.10
port: 8262
deploy_dir: "/dm-deploy"
data_dir: "/dm-data"
- host: 10.0.2.20
port: 8262
deploy_dir: "/dm-deploy"
data_dir: "/dm-data"
# 部署 DM 集群
tiup dm deploy dm-prod v8.5.0 dm-topology.yaml --user root
# 启动集群
tiup dm start dm-prod
# 查看状态
tiup dm display dm-prod
2.3 二进制部署(无 TiUP 环境)
# 下载 DM 二进制
wget https://download.pingcap.org/dm-v8.5.0-linux-amd64.tar.gz
tar -xzf dm-v8.5.0-linux-amd64.tar.gz
# 启动 DM-master
./bin/dm-master --config=dm-master.toml
# 启动 DM-worker
./bin/dm-worker --config=dm-worker.toml
# dm-master.toml
name = "dm-master-1"
data-dir = "/dm-data/master"
peer-urls = "0.0.0.0:8291"
advertise-peer-urls = "http://10.0.1.50:8291"
master-addr = ":8261"
advertise-addr = "10.0.1.50:8261"
initial-cluster = "dm-master-1=http://10.0.1.50:8291,dm-master-2=http://10.0.1.51:8291,dm-master-3=http://10.0.1.52:8291"
# dm-worker.toml
name = "dm-worker-1"
master-addr = "10.0.1.50:8261,10.0.1.51:8261,10.0.1.52:8261"
worker-addr = ":8262"
join = "http://10.0.1.50:8261"
三、配置数据源
3.1 数据源配置文件
# source1.yaml
source-id: "mysql-source-1"
# MySQL 连接信息
from:
host: "192.168.1.100"
port: 3306
user: "root"
password: "encrypted_password" # 使用 dmctl encrypt 加密
max-allowed-packet: 67108864 # 64MB
# 启用 relay log(可选,用于断点续传)
enable-relay: true
relay-binlog-name: "mysql-bin.000001"
relay-binlog-gtid: ""
# 安全连接(可选)
security:
ssl-ca: "/path/to/ca.pem"
ssl-cert: "/path/to/cert.pem"
ssl-key: "/path/to/key.pem"
# 加密密码
tiup dmctl --master-addr 10.0.1.50:8261 operate-source create source1.yaml
# 查看数据源
tiup dmctl --master-addr 10.0.1.50:8261 operate-source show
# 查看数据源状态
tiup dmctl --master-addr 10.0.1.50:8261 query-status --source mysql-source-1
3.2 上游 MySQL 准备
DM 需要 MySQL 开启 binlog 并设置正确的格式:
-- 检查 MySQL binlog 配置
SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_format';
SHOW VARIABLES LIKE 'binlog_row_image';
-- 要求:
-- log_bin = ON
-- binlog_format = ROW
-- binlog_row_image = FULL
-- 如果未开启,修改 my.cnf:
-- [mysqld]
-- log-bin = mysql-bin
-- binlog-format = ROW
-- binlog-row-image = FULL
-- server-id = 1 # 每个 MySQL 实例必须唯一
DM 所需的 MySQL 权限:
-- 创建 DM 专用用户
CREATE USER 'dm_worker'@'%' IDENTIFIED BY 'your_password';
GRANT SELECT, RELOAD, LOCK TABLES, REPLICATION CLIENT, REPLICATION SLAVE, PROCESS ON *.* TO 'dm_worker'@'%';
FLUSH PRIVILEGES;
四、任务配置详解
4.1 完整任务配置
# task-full.yaml — 全量 + 增量迁移
---
# 任务名称(全局唯一)
name: "mysql-to-tidb-prod"
# 任务模式: full(全量), incremental(增量), all(全量+增量)
task-mode: "all"
# 是否启用增量同步的 relay log
is-sharding: false
# 处理不兼容的 DDL 策略
# - "fail" : 遇到不兼容 DDL 停止任务(默认)
# - "ignore": 忽略不兼容 DDL
ignore-checking-items: ["auto_increment_ID"]
# 目标 TiDB 配置
target-database:
host: "10.0.1.14"
port: 4000
user: "root"
password: ""
max-allowed-packet: 67108864
session:
tidb_txn_mode: "pessimistic"
tidb_disable_txn_auto_retry: "off"
# 上游 MySQL 实例列表
mysql-instances:
- source-id: "mysql-source-1"
# 库表过滤
block-allow-list: "prod-filter"
# 全量导出配置引用
mydumper-config-name: "global-dump"
# 全量导入配置引用
loader-config-name: "global-load"
# 增量同步配置引用
syncer-config-name: "global-sync"
# 表路由规则
route-rules: ["route-rule-1"]
# 列过滤规则
filter-rules: ["filter-rule-1"]
# 在线 DDL 处理方式
online-ddl-scheme: "gh-ost"
- source-id: "mysql-source-2"
block-allow-list: "prod-filter"
mydumper-config-name: "global-dump"
loader-config-name: "global-load"
syncer-config-name: "global-sync"
# 库表过滤规则
block-allow-list:
prod-filter:
do-dbs: ["ecommerce", "user_center"]
do-tables:
- db-name: "ecommerce"
tbl-name: "~^(orders|order_items|products|categories)$"
- db-name: "user_center"
tbl-name: "~^(users|user_profiles|addresses)$"
ignore-dbs: ["test", "performance_schema", "mysql"]
# Mydumper 全量导出配置
mydumpers:
global-dump:
rows: 200000 # 每个文件最大行数
threads: 8 # 导出并发线程数
chunk-filesize: "64" # 每个文件大小 (MB)
extra-args: "--no-views --no-triggers" # 额外参数
# Loader 全量导入配置
loaders:
global-load:
pool-size: 16 # 导入并发数
dir: "./dumped_data" # 临时数据目录
sorting-dir-physical: "/tmp/sorting"
# 导入模式: "logical"(默认)或 "physical"
import-mode: "logical"
# on-duplicate: "replace" 或 "error" 或 "ignore"
on-duplicate: "replace"
# Syncer 增量同步配置
syncers:
global-sync:
worker-count: 16 # 同步并发数
batch: 100 # 每批处理的 binlog 事件数
queue-size: 1024 # binlog 事件队列大小
# 最大重试次数
max-retry: 100
# 自动跳过 DDL 错误的表(谨慎使用)
# skip-ddls: ["ALTER TABLE"]
# 表路由规则(分库分表合并时使用)
routes:
route-rule-1:
# 源库匹配模式
schema-pattern: "ecommerce_*"
# 源表匹配模式
table-pattern: "orders_*"
# 目标库名
target-schema: "ecommerce"
# 目标表名
target-table: "orders"
# 列过滤规则
filters:
filter-rule-1:
schema-pattern: "ecommerce"
table-pattern: "orders"
# 过滤掉特定类型的 binlog 事件
events: ["truncate", "drop table"]
sql-pattern: ["^DROP TABLE", "^TRUNCATE"]
action: Ignore # Ignore 或 Do
4.2 最小任务配置
对于简单的全库迁移:
# task-minimal.yaml
name: "simple-migration"
task-mode: "all"
target-database:
host: "10.0.1.14"
port: 4000
user: "root"
password: ""
mysql-instances:
- source-id: "mysql-source-1"
# 不做任何过滤,迁移所有库表
五、分库分表合并迁移
5.1 场景说明
MySQL 分库分表:
db_01.users ─┐
db_02.users ─┼──> TiDB: ecommerce.users(合并为一张表)
db_03.users ─┘
db_01.orders_01 ─┐
db_01.orders_02 ─┤
db_02.orders_01 ─┼──> TiDB: ecommerce.orders
db_02.orders_02 ─┘
5.2 乐观 shard-merge 配置
# task-sharding-optimistic.yaml
name: "sharding-merge-optimistic"
task-mode: "all"
is-sharding: true
shard-mode: "optimistic" # 乐观模式:假设 DDL 顺序一致
target-database:
host: "10.0.1.14"
port: 4000
user: "root"
mysql-instances:
- source-id: "mysql-source-1"
block-allow-list: "shard-filter"
route-rules: ["shard-route"]
syncer-config-name: "shard-sync"
block-allow-list:
shard-filter:
do-dbs: ["db_*"]
do-tables:
- db-name: "db_*"
tbl-name: "~^(users|orders)_"
routes:
shard-route:
schema-pattern: "db_*"
table-pattern: "users_*"
target-schema: "ecommerce"
target-table: "users"
syncers:
shard-sync:
worker-count: 16
batch: 100
# 乐观模式下 DM 假设所有分片的 DDL 按相同顺序执行
# 如果 DDL 顺序不一致,可能导致数据不一致
5.3 悲观 shard-merge 配置
# task-sharding-pessimistic.yaml
name: "sharding-merge-pessimistic"
task-mode: "all"
is-sharding: true
shard-mode: "pessimistic" # 悲观模式:等待所有分片 DDL 到达后再执行
5.4 处理分片 DDL 冲突
# 查看 DDL 锁
tiup dmctl --master-addr 10.0.1.50:8261 ddl --name sharding-merge-optimistic
# 解锁 DDL 锁(跳过某个分片的 DDL)
tiup dmctl --master-addr 10.0.1.50:8261 unlock-ddl-lock \
--task sharding-merge-optimistic \
<lock-id>
# 强制执行 DDL
tiup dmctl --master-addr 10.0.1.50:8261 unlock-ddl-lock \
--task sharding-merge-optimistic \
--exec 1 \
<lock-id>
六、任务管理
6.1 启动任务
# 创建并启动任务
tiup dmctl --master-addr 10.0.1.50:8261 start-task task-full.yaml
# 指定 Worker 启动(多 Worker 场景)
tiup dmctl --master-addr 10.0.1.50:8261 start-task task-full.yaml \
--source-workers mysql-source-1
6.2 查看任务状态
# 查看任务整体状态
tiup dmctl --master-addr 10.0.1.50:8261 query-status mysql-to-tidb-prod
# 查看子任务详情
tiup dmctl --master-addr 10.0.1.50:8261 query-status mysql-to-tidb-prod \
--source mysql-source-1
# 输出示例:
# {
# "result": true,
# "msg": "",
# "sources": [
# {
# "result": true,
# "source": "mysql-source-1",
# "worker": "10.0.2.10:8262",
# "subTaskStatus": [
# {
# "name": "mysql-to-tidb-prod",
# "stage": "Running",
# "unit": "Sync", ← 当前执行阶段
# "sync": {
# "totalEvents": 12345,
# "totalTps": 567,
# "recentTps": 123,
# "masterBinlog": "(mysql-bin.000045, 12345678)",
# "syncerBinlog": "(mysql-bin.000045, 12345678)",
# "binlogType": "remote"
# }
# }
# ]
# }
# ]
# }
6.3 暂停/恢复/停止任务
# 暂停任务
tiup dmctl --master-addr 10.0.1.50:8261 pause-task mysql-to-tidb-prod
# 暂停特定数据源的任务
tiup dmctl --master-addr 10.0.1.50:8261 pause-task mysql-to-tidb-prod \
--source mysql-source-1
# 恢复任务
tiup dmctl --master-addr 10.0.1.50:8261 resume-task mysql-to-tidb-prod
# 停止任务(删除任务配置)
tiup dmctl --master-addr 10.0.1.50:8261 stop-task mysql-to-tidb-prod
6.4 修改任务配置
# 更新任务配置
tiup dmctl --master-addr 10.0.1.50:8261 update-task mysql-to-tidb-prod \
task-updated.yaml
# 更新数据源配置
tiup dmctl --master-addr 10.0.1.50:8261 update-config mysql-source-1 \
--task mysql-to-tidb-prod \
source1-updated.yaml
七、增量同步与切换
7.1 增量同步状态判断
# 查看同步延迟
tiup dmctl --master-addr 10.0.1.50:8261 query-status mysql-to-tidb-prod
# 关键字段:
# masterBinlog: MySQL 当前最新 binlog 位置
# syncerBinlog: DM 已同步到的 binlog 位置
# 当两者一致时,说明增量已追平
7.2 切换流量
切换流程:
1. 应用停止向 MySQL 写入
↓
2. 等待 DM 追平最后一个 binlog 事件
(masterBinlog == syncerBinlog)
↓
3. 数据一致性校验
(使用 sync-diff-inspector)
↓
4. 切换应用连接至 TiDB
↓
5. 观察 TiDB 运行状态
↓
6. 停止 DM 任务
# 一致性校验
sync-diff-inspector \
--config=check-config.yaml
# 停止 DM 任务
tiup dmctl --master-addr 10.0.1.50:8261 stop-task mysql-to-tidb-prod
八、DM Web UI
DM 提供 Web 界面管理迁移任务:
访问: http://10.0.1.50:8261
功能:
├── 任务列表 — 查看所有任务状态
├── 任务详情 — 查看同步进度、延迟、binlog 位置
├── 数据源管理 — 添加/删除数据源
├── 告警信息 — 查看任务告警
└── 操作日志 — 审计操作记录
九、故障排查
9.1 常见错误
| 错误 | 原因 | 解决 |
|---|---|---|
code=38020 |
binlog 格式不正确 | 检查 binlog_format=ROW |
code=36027 |
上游连接失败 | 检查网络和 MySQL 状态 |
code=30064 |
DDL 执行失败 | 检查 DDL 语法兼容性 |
code=20032 |
数据源 ID 冲突 | 修改 source-id |
code=30015 |
Loader 导入失败 | 检查 TiDB 连接和权限 |
9.2 处理失败的 DDL
# 查看失败的 DDL
tiup dmctl --master-addr 10.0.1.50:8261 operate-schema show \
--task mysql-to-tidb-prod \
--source mysql-source-1 \
--database ecommerce \
--table orders
# 跳过 DDL(手动修复后)
tiup dmctl --master-addr 10.0.1.50:8261 operate-schema set \
--task mysql-to-tidb-prod \
--source mysql-source-1 \
--database ecommerce \
--table orders \
"CREATE TABLE orders (...)" # 正确的 schema
9.3 日志查看
# DM-master 日志
tail -100 /dm-deploy/dm-master-8261/log/dm-master.log
# DM-worker 日志
tail -100 /dm-deploy/dm-worker-8262/log/dm-worker.log
# 查看特定任务的日志
grep "mysql-to-tidb-prod" /dm-deploy/dm-worker-8262/log/dm-worker.log
十、性能调优
10.1 全量迁移调优
mydumpers:
global-dump:
threads: 16 # 增加导出并发
rows: 500000 # 增大文件行数,减少文件数量
loaders:
global-load:
pool-size: 32 # 增加导入并发
import-mode: "physical" # 大数据量使用物理导入
10.2 增量同步调优
syncers:
global-sync:
worker-count: 32 # 增加同步并发
batch: 500 # 增大批次大小
queue-size: 4096 # 增大队列
10.3 Relay Log 调优
# 在 source 配置中启用 relay log
enable-relay: true
# relay log 目录(需要足够空间)
relay-dir: "/dm-data/relay"
# 优点: 断点续传时不需重新拉取远端 binlog
# 缺点: 占用额外磁盘空间