跨云迁移实战
AWS ↔ 阿里云 ↔ SkySQL ↔ 自建——双写切流、DTS、Debezium 三种方案对比
何时需要跨云迁移
- 业务出海 / 回国
- 成本优化
- 厂商锁定脱钩
- 合规重定区
三种迁移方案
方案 1:离线全量 + 短停机
+---------+ dump +-----------+
| Source | ---------> | S3 |
+---------+ +-----+-----+
|
v
+-----------+
| Target |
+-----------+- 优点:简单可靠、无双写复杂度
- 缺点:停机几小时到几天,看库大小
- 适合:可停机的小库
方案 2:DTS / 第三方托管
| 工具 | 源 → 目标 |
|---|---|
| AWS DMS | AWS Aurora/RDS → 任意 MySQL 兼容 |
| 阿里云 DTS | 阿里 RDS → 任意目标 |
| 腾讯 DTS | 类似 |
| Striim | 任意 → 任意 |
| Fivetran | 偏 ETL,不是 0 停机 |
- 优点:托管、CDC 自动
- 缺点:贵、特定路径才支持
方案 3:自建 Debezium + 应用双写
最灵活但要自己运维。下面详细讲。
双写切流完整流程
1. 准备 target 端
# 在目标环境拉起 MariaDB(自建或托管 RDS)
# 确保版本、字符集、collation 与 source 一致
mariadb -h target -e "SHOW VARIABLES LIKE '%char%'"
mariadb -h target -e "SHOW VARIABLES LIKE '%coll%'"2. 初始全量同步
从 source 只读副本拿 dump,避免影响生产:
mysqldump -h source-readonly \
--single-transaction --routines --triggers --events \
--set-gtid-purged=OFF \
--master-data=2 \
--all-databases > dump.sql
# 替换不兼容的 collation(MySQL 8 → MariaDB)
sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g' dump.sql
sed -i 's/utf8mb4_0900_as_cs/utf8mb4_unicode_ci/g' dump.sql
# 导入 target
mariadb -h target -uroot -p < dump.sql
# 记录 binlog 起点
head -100 dump.sql | grep CHANGE3. 启动 Debezium 持续同步
# debezium-source.json
{
"name": "source-binlog",
"config": {
"connector.class": "io.debezium.connector.mysql.MySqlConnector",
"database.hostname": "source-readonly",
"database.port": "3306",
"database.user": "debezium",
"database.password": "...",
"database.server.id": "184054",
"database.server.name": "source",
"database.include.list": "app",
"table.include.list": "app.*",
"database.history.kafka.bootstrap.servers": "kafka:9092",
"database.history.kafka.topic": "schema-history",
"snapshot.mode": "schema_only",
"include.schema.changes": "true"
}
}下游 sink:
# jdbc-sink.json
{
"name": "target-sink",
"config": {
"connector.class": "io.confluent.connect.jdbc.JdbcSinkConnector",
"connection.url": "jdbc:mariadb://target:3306/app",
"connection.user": "sink",
"connection.password": "...",
"topics.regex": "source\\.app\\..*",
"insert.mode": "upsert",
"pk.mode": "record_key",
"transforms": "unwrap,route",
"transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
"transforms.route.type": "org.apache.kafka.connect.transforms.RegexRouter",
"transforms.route.regex": "source\\.app\\.(.+)",
"transforms.route.replacement": "$1"
}
}4. 应用层双写
应用代码改成"主写 source,shadow 写 target":
async function write(sql: string, params: any[]) {
await sourcePool.query(sql, params); // 真主
targetPool.query(sql, params).catch((e) => { // shadow,不阻塞主
metrics.shadowWriteFail.inc();
log.warn('shadow write failed', { e, sql });
});
}双写的意义:CDC 可能有 bug 或延迟。双写是兜底。
5. 数据一致性对比
# 用 pt-table-checksum
pt-table-checksum --replicate=app.checksums \
h=source --recursion-method=hosts
# 或自写:抽样对比
mariadb -h source -e "SELECT id, MD5(CONCAT_WS(',', col1, col2)) FROM users ORDER BY id LIMIT 1000" > s.csv
mariadb -h target -e "SELECT id, MD5(CONCAT_WS(',', col1, col2)) FROM users ORDER BY id LIMIT 1000" > t.csv
diff s.csv t.csv至少跑 24 小时观察。
6. 切读流量
应用层 feature flag 灰度:
const useTarget = ff.isEnabled('use_target_db', userId);
const pool = useTarget ? targetPool : sourcePool;5% → 10% → 50% → 100%,每档观察 24 小时。
7. 切写流量
const writeTarget = ff.isEnabled('write_target_only');
if (writeTarget) {
await targetPool.query(sql, params);
sourcePool.query(sql, params).catch(/* shadow */); // 反向 shadow,应急回滚用
} else {
await sourcePool.query(sql, params);
targetPool.query(sql, params).catch(/* shadow */);
}8. 切完后保留至少 2 周
留 source 端只读,应急时能切回。2 周后下线 source。
几种典型路径
AWS RDS → 阿里云 RDS
坑:
- 跨区网络延迟高(150ms+),CDC 延迟会拉到秒级
- 阿里云 RDS 默认版本旧,需要 source 端先降版本兼容
- 阿里云 RDS 不给 SUPER 权限,复制起点设置略麻烦
推荐:阿里云 DTS(按量付费,专门优化)
阿里云 → AWS RDS
坑:
- 阿里 RDS 不开 binlog 给外部?要开"日志服务"或申请
- 字符集兼容(阿里偏旧版本)
推荐:DataGrip / 自建 Debezium,DTS 反向支持差
任意 → SkySQL
SkySQL 提供专门的 ingestion 工具:
skysql ingest start \
--source-uri=mysql://source-host:3306/app \
--target-service=skysql-prod支持 ColumnStore / Xpand 目标,普通 MariaDB 也行。
任意 → 自建 K8s
# 用 mariadb-operator 创 target
kubectl apply -f mariadb-prod.yaml
# 等就绪
kubectl wait --for=condition=Ready mariadb/mariadb-prod --timeout=10m
# 接 Debezium
helm install debezium debezium/debezium-operator详见 容器化。
Aurora → MariaDB
特殊路径,详见 从 Aurora 迁移。
回滚预案
任何切流都必须有回滚预案:
- 读流量切回:feature flag 一键
- 写流量切回:feature flag 一键 + 重放最近 N 分钟 binlog
- 完全放弃 target:source 始终有完整数据
"回滚比切流难得多"——如果切流前没演练回滚,等于裸奔。
验证清单
- 字符集 / collation 完全一致
- 时区一致(
SELECT @@time_zone) - sql_mode 一致
- 所有 stored procedure / trigger / event 复制过去
- 主键自增起点:target 比 source 大 100 万(防冲突)
- 触发器、外键、CHECK 约束
- 全部 user 重新 GRANT
- 全部 view 重建
- 应用配置切完
- 监控 dashboard 切完
- 备份策略迁移到新环境
- DNS / 域名 / VPC 路由切完
- 旧环境断网测试,确认无应用调用
时间预估
| 库大小 | 全量 dump | 全量恢复 | CDC 追平 | 总停机(双写) |
|---|---|---|---|---|
| 10 GB | 10 分钟 | 30 分钟 | 即时 | 1 分钟(切流) |
| 100 GB | 2 小时 | 6 小时 | < 5 分钟 | 1 分钟 |
| 1 TB | 12 小时 | 36 小时 | 30 分钟 | 5 分钟 |
| 10 TB | 数天 | 数天 | 数小时 | 视情况 |
10 TB+ 建议用 mariabackup + xtrabackup 增量 或物理拷贝。