数据同步与迁移方案
# 10.数据同步与迁移方案
本篇定位:数据同步是分布式系统的"血液循环"——不论是数据库主从、跨库异构、机房双活、业务搬迁,本质都是"让一份数据在多处保持一致"。本文从一次跨机房迁移的事故讲起,回答三个核心问题——同步和迁移有什么区别?业界三种方案怎么选?大数据量怎么平滑迁移?
# 目录介绍
# 01.一次跨机房的迁移
# 1.1 业务搬迁背景
某金融公司因为合规要求,需要把核心业务从公有云迁移到自建机房。涉及:
- 核心订单库 800GB / 1.2 亿条数据
- 用户库 200GB / 5000 万账户
- 资金流水库 1.5TB / 3 亿条记录
- 业务必须 7×24 不停机
# 1.2 三天迁移的事故
团队选了 "导出 + 导入 + 切流" 的"传统"方案:
gantt
title 这次失败的迁移时间线
dateFormat YYYY-MM-DD HH:mm
section Day 1
导出全部数据 :crit, e1, 2022-03-15 02:00, 8h
传输到新机房 :crit, e2, after e1, 4h
section Day 2
导入新机房 :crit, i1, 2022-03-15 14:00, 12h
数据校验 :crit, v1, after i1, 6h
发现 380 万条不一致 :milestone, 2022-03-16 06:00, 0h
section Day 3
紧急修复 :crit, fix1, 2022-03-16 06:00, 8h
再次校验 :crit, v2, after fix1, 4h
切流失败回滚 :crit, rb, 2022-03-16 18:00, 6h
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
为什么会出 380 万条不一致?因为导出期间业务还在持续写入旧库,导出的是某个时间点的快照,之后的所有写入都丢了。
# 1.3 反思迁移设计
事后这个团队学到了三个最重要的认知:
- 大数据量迁移必须"全量+增量"——纯全量导出导入根本不行
- 数据校验是迁移成败的分水岭——不校验的迁移就是赌博
- 切流必须可灰度——一刀切流要么不出事,出事就是 P0
这次惨痛的教训背后,是对"业务无感迁移"这个本质要求的低估。
# 02.要解决的核心矛盾
# 2.1 同步与迁移区别
很多人把"同步"和"迁移"混为一谈,其实有本质区别:
| 维度 | 数据同步 | 数据迁移 |
|---|---|---|
| 目的 | 持续保持多处一致 | 一次性搬到新地方 |
| 持续时间 | 永久 | 有终点 |
| 业务感知 | 长期共存 | 切换后旧的下线 |
| 典型场景 | 主从 / 异地多活 / 异构索引 | 上云 / 换数据库 / 重构 |
| 复杂度 | 持续治理 | 一次性挑战 |
很多迁移失败的根因是把它当成了"一次性同步"——而实际上它是"同步 + 校验 + 切流 + 下线"的完整工程。
# 2.2 一致性与时延
| 一致性 | 同步时延 | 业务影响 |
|---|---|---|
| 强一致 | 毫秒级 | 写入慢但读取一致 |
| 准实时 | 秒级 | 写入快但有短暂延迟 |
| 最终一致 | 分钟级 | 性能最好但容忍滞后 |
铁律:同步时延越低,对源系统的侵入越大。
# 2.3 业务连续与数据搬运
迁移最大的挑战是业务一边在跑,数据一边在搬。三个动态难题:
graph TB
A[难题1: 历史数据搬运过程中<br/>新写入怎么处理?] --> B[必须有增量同步机制]
C[难题2: 双写期间<br/>两边数据怎么保证一致?] --> D[必须有校验对账]
E[难题3: 切流时<br/>万一新库出问题怎么办?] --> F[必须能秒级切回]
style B fill:#fff3e0
style D fill:#fff3e0
style F fill:#fff3e0
2
3
4
5
6
7
8
9
10
# 2.4 同步迁移的本质
数据同步迁移 = 在"业务持续运行"的前提下,把数据"无损"地从 A 搬到 B(或同时存在于 A 和 B)
它的核心追求是 不丢、不错、不停业务。
# 03.业界主流方案
# 3.1 三类同步方式
应用层双写 应用代码同时写两个库。简单直接但侵入业务。
binlog 订阅 监听数据库 binlog(或 redo log),异步同步到目标。不侵入业务,是大数据量的首选。
ETL 批量同步 定时把数据批量从源拉到目标。延迟高但简单,适合数仓场景。
flowchart LR
A[应用层双写<br/>实时但侵入业务] --> B[binlog 订阅<br/>实时且无侵入]
B --> C[ETL 批量<br/>延迟但简单]
A1[侵入应用] -.- A
B1[业务无感] -.- B
C1[小时/天级延迟] -.- C
style A fill:#ffebee
style B fill:#e8f5e8
style C fill:#fff3e0
2
3
4
5
6
7
8
9
10
11
# 3.2 横向对比矩阵
| 维度 | 应用层双写 | binlog 订阅 | ETL 批量 |
|---|---|---|---|
| 实时性 | 毫秒 | 秒级 | 分钟到天 |
| 业务侵入 | 强 | 无 | 无 |
| 数据一致性 | 取决于实现 | 强(基于 redo log) | 弱 |
| 资源消耗 | 应用层负担 | 数据库 binlog | 定时任务 |
| 失败恢复 | 复杂 | 偏简单(位点) | 简单(重跑) |
| 适用场景 | 小流量 / 同构 | 实时同步 / 异构索引 | 数仓 / 报表 |
# 3.3 典型工具选型
| 工具 | 类型 | 主要场景 |
|---|---|---|
| Canal | binlog 订阅 | MySQL → 任意目标,国内最流行 |
| Maxwell | binlog 订阅 | MySQL → Kafka,简单易用 |
| Debezium | binlog 订阅 | 多数据库支持,CDC 平台首选 |
| DataX | ETL | 阿里开源,异构批量同步 |
| Sqoop | ETL | Hadoop 生态,关系库 ↔ HDFS |
| Flink CDC | 流式 | Debezium + Flink,强大但重 |
| DTS(云厂商) | 综合 | 上云首选,功能完整但要钱 |
实战推荐:
- 数据库实时同步:Canal / Debezium
- 大数据 ETL:DataX
- 异构索引(如 ES):Canal + Kafka + 消费者
- 跨云迁移:云厂商 DTS
# 04.设计核心原则
# 4.1 业务无感原则
铁律:迁移过程对业务无感。
| 业务感知 | 不可接受 |
|---|---|
| 接口超时 | ❌ |
| 数据短暂丢失 | ❌ |
| 写入失败 | ❌ |
| 报错率上升 | ❌ |
唯一可以接受的"感知":极短暂(几秒)的切流切换窗口。
# 4.2 可观测原则
没有监控的迁移 = 蒙眼开车。必须有的观测点:
mindmap
root((迁移可观测))
同步链路
源库 binlog 位点
管道延迟
消费者堆积
数据一致性
行数差异
字段差异
抽样比对
业务影响
接口耗时
错误率
DB 负载
资源消耗
CPU / 内存
网络带宽
磁盘 IO
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 4.3 可回滚原则
任何阶段都要能回滚:
| 阶段 | 回滚方式 |
|---|---|
| 全量同步期 | 直接停掉同步任务 |
| 增量同步期 | 停同步 + 清理目标库 |
| 双写期 | 关闭新库写入 |
| 切流期 | 流量切回 100% 旧库 |
| 切完观察期 | 旧库还在,可一键切回 |
铁律:旧库至少保留 30 天才能彻底下线。
# 4.4 增量优先原则
反直觉:先做增量同步链路,再做全量同步。
sequenceDiagram
participant Old as 旧库
participant Pipe as 同步管道
participant New as 新库
Note over Pipe: 步骤1: 启动 binlog 监听<br/>记下当前位点 P1
Pipe->>Old: 监听 binlog (从位点 P1 开始)
Note over Old,New: 步骤2: 全量同步<br/>(同时增量也在缓存)
Old->>New: 全量数据 dump → 灌入新库
Note over Pipe,New: 步骤3: 应用增量<br/>(从 P1 开始的所有变更)
Pipe->>New: 应用 P1 之后的所有 binlog
Note over Pipe,New: 步骤4: 实时同步追上<br/>持续应用最新 binlog
2
3
4
5
6
7
8
9
10
11
12
13
14
15
为什么这样:开篇那个事故就是因为没有先开启增量同步——全量导出期间的写入全丢了。先开增量,全量怎么导都不会丢。
# 05.同步方案落地
# 5.1 binlog 同步原理
graph TB
subgraph "源 MySQL"
Master[(Master)]
BL[binlog 文件]
Master -->|事务提交后写入| BL
end
subgraph "同步组件"
Canal[Canal Server]
Canal -->|伪装成 Slave 拉取| BL
Canal -->|解析为结构化事件| Parser[binlog 解析器]
end
subgraph "下游"
MQ[Kafka]
Parser --> MQ
ESConsumer[ES 消费者] --> MQ
DBConsumer[新库消费者] --> MQ
ESConsumer --> ES[(Elasticsearch)]
DBConsumer --> NewDB[(新 MySQL)]
end
style Canal fill:#fff3e0
style MQ fill:#e8f5e8
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
核心原理:
- MySQL 写入时按事务粒度记录 binlog
- Canal 伪装成从库拉取 binlog
- 解析为结构化事件(INSERT / UPDATE / DELETE + 数据)
- 推到 Kafka 让多个消费者订阅
优势:
- 业务无感:不改任何应用代码
- 强一致性:基于事务日志,不会丢
- 可重放:消费者保存位点,故障可重放
# 5.2 全量+增量同步
完整迁移流程的 8 个步骤:
flowchart TD
S1[1. 评估数据量和业务] --> S2[2. 建立 binlog 监听]
S2 --> S3[3. 记录起始位点]
S3 --> S4[4. 全量数据迁移]
S4 --> S5[5. 应用增量到当前]
S5 --> S6[6. 数据校验]
S6 --> S7[7. 灰度切流]
S7 --> S8[8. 完全切流]
S6 -.发现不一致.-> Fix[修复并重新校验]
Fix --> S6
S7 -.异常.-> RollBack[回滚]
RollBack --> S6
style S2 fill:#fff3e0
style S6 fill:#fff3e0
style S7 fill:#ffebee
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 5.3 异构数据同步
典型场景:MySQL → ES(搜索)/ Hive(报表)/ Redis(缓存)
graph TB
MySQL[(MySQL 业务库)] --> Canal[Canal]
Canal --> Kafka[Kafka 消息队列]
Kafka --> ES_Consumer[ES 消费者]
Kafka --> Hive_Consumer[Hive 消费者]
Kafka --> Redis_Consumer[Redis 消费者]
ES_Consumer --> ES[(Elasticsearch)]
Hive_Consumer --> Hive[(数据仓库)]
Redis_Consumer --> Redis[(Redis)]
style Canal fill:#fff3e0
style Kafka fill:#e8f5e8
2
3
4
5
6
7
8
9
10
11
12
13
14
关键设计:
- 一个 Canal 可以接多个消费者(不要每种目标库开一个 Canal)
- Kafka 作为缓冲层,下游故障不影响主链路
- 每个消费者独立保存位点,互不影响
# 5.4 双向同步与冲突解决
异地多活场景:两个机房都接受写入,数据需要双向同步。
graph LR
DC1[(机房A)] -->|同步| DC2[(机房B)]
DC2 -->|同步| DC1
DC1 -.冲突.-> Conflict[同一条数据<br/>两边同时改]
style Conflict fill:#ffebee
2
3
4
5
6
7
冲突解决方案:
| 方案 | 思路 | 适用 |
|---|---|---|
| 业务分区 | 每个用户固定路由到一个机房 | 大多数业务(最优) |
| 时间戳胜出 | 后写入的覆盖先写入的 | 容忍少量丢失 |
| 版本向量 | 每条数据带版本号比较 | 复杂场景 |
| CRDT | 冲突自由数据结构 | 计数器 / 集合等 |
实战推荐:90% 选业务分区——避免冲突好过解决冲突。
# 06.迁移方案落地
# 6.1 整体迁移流程
完整迁移项目的标准节奏:
gantt
title 标准迁移项目时间线 (3-6个月)
dateFormat YYYY-MM-DD
section 准备阶段
评估和方案设计 :p1, 2022-01-01, 30d
工具和代码准备 :p2, after p1, 30d
section 同步阶段
建立增量同步 :s1, after p2, 7d
全量数据同步 :s2, after s1, 14d
section 校验阶段
全量数据校验 :v1, after s2, 14d
问题修复 :v2, after v1, 14d
section 切流阶段
双写双读对比 :c1, after v2, 14d
灰度切流 1%->100% :c2, after c1, 14d
section 收尾阶段
观察期 :o1, after c2, 30d
旧库下线 :o2, after o1, 7d
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 6.2 数据校验工具
校验是迁移成败的"分水岭"。必须有的校验工具:
-- 1. 行数校验
SELECT COUNT(*) FROM old_db.t_order;
SELECT COUNT(*) FROM new_db.t_order;
-- 2. 抽样校验(每个分片随机 1000 条)
SELECT * FROM t_order TABLESAMPLE BERNOULLI(0.001);
-- 3. 业务指标校验
SELECT SUM(amount), COUNT(DISTINCT user_id) FROM t_order;
-- 4. 边界校验
SELECT MAX(id), MIN(id), MAX(created_at), MIN(created_at) FROM t_order;
2
3
4
5
6
7
8
9
10
11
12
完整校验工具应该具备:
- 全量行数对比(按时间窗口分批)
- 抽样字段对比(每天抽 N 万条)
- 业务指标对比(总金额、UV、PV)
- 不一致数据导出(修复用)
- 持续校验(双写期间天天跑)
# 6.3 切流策略
渐进式切流是必经之路:
flowchart LR
P0[0% 全量旧库] --> P1[1% 灰度新库]
P1 -->|24h观察| P5[5% 新库]
P5 -->|24h| P20[20% 新库]
P20 -->|24h| P50[50% 新库]
P50 -->|24h| P100[100% 新库]
P100 -->|7天| Off[旧库停写]
Off -->|30天| Done[完全下线]
style P1 fill:#fff3e0
style P50 fill:#e8f5e8
style P100 fill:#e8f5e8
style Off fill:#ffebee
2
3
4
5
6
7
8
9
10
11
12
13
每一档都要看:
- 业务错误率没有上升
- 接口耗时没有恶化
- 数据一致性持续 OK
- DB 负载在可控范围
# 6.4 回滚兜底机制
graph TB
A[实时监控告警] --> B{触发回滚?}
B -->|错误率> 0.5%| C[自动回滚]
B -->|耗时翻倍| C
B -->|数据不一致> 100| C
C --> D[流量切回旧库]
D --> E[新库停写]
E --> F[根因分析]
F --> G[修复后再来一次]
style C fill:#ffebee
style D fill:#fff3e0
2
3
4
5
6
7
8
9
10
11
12
13
关键能力:回滚必须 < 1 分钟生效。所以路由层(如 Apollo 配置 / nginx 路由表)要支持瞬间切换。
# 07.常见陷阱与反例
# 7.1 同步延迟反例
反例:某团队 binlog 同步链路出现 30 分钟延迟,但下游业务没意识到,用户改了密码后 30 分钟内还能用旧密码登录——直接爆出安全事故。
教训:
- 同步延迟必须监控告警(阈值如 1 分钟)
- 关键业务(鉴权 / 资金)应该走强一致路径,不依赖异步同步
- 延迟超过阈值应自动降级(如读改为强制读主库)
# 7.2 数据丢失反例
反例:开篇那个 380 万条丢失——全量导出期间没开增量同步。
教训:
- 永远先开增量再做全量
- 全量完成后必须应用增量"追上"
- 校验必须严格
# 7.3 双写不一致反例
反例:应用层双写,"先写老库再写新库"。但某次老库成功新库失败,应用没处理——老库有这条数据,新库没有。
教训:
- 应用层双写必须考虑部分失败场景
- 推荐方案:主写老库 + binlog 同步到新库(避免应用层双写的复杂度)
- 或者用分布式事务 + 补偿机制
mindmap
root((三大反例))
同步延迟
没有延迟监控
关键业务依赖异步
用户读到旧数据
数据丢失
全量未先开增量
位点丢失
未校验直接切流
双写不一致
部分失败无处理
没补偿机制
最终新老不一致
2
3
4
5
6
7
8
9
10
11
12
13
14
# 08.演进路线
# 8.1 V1 应用层双写
特征:业务量小、迁移规模小。
做法:
- 应用代码直接双写
- 简单的失败重试
- 人工对账
适用阶段:早期 / 小规模
# 8.2 V2 binlog 订阅
特征:数据量大、业务复杂。
做法:
- Canal / Debezium 订阅 binlog
- Kafka 缓冲
- 多消费者订阅
- 完善的监控
适用阶段:中大型业务、跨库异构
# 8.3 V3 数据集成平台
特征:公司级数据集成、多源多目标。
做法:
- 自建数据集成平台(如阿里 DataWorks)
- 可视化配置同步任务
- 统一的监控、告警、调度
- 数据血缘和数据治理
适用阶段:大型公司、多团队多业务线
flowchart LR
V1[V1 应用层双写<br/>简单场景] --> V2[V2 binlog 订阅<br/>中大型]
V2 --> V3[V3 数据集成平台<br/>公司级]
style V1 fill:#e3f2fd
style V2 fill:#e8f5e8
style V3 fill:#fff3e0
2
3
4
5
6
7
# 09.总结与决策
# 9.1 迁移上线检查表
启动数据迁移项目前对照这张清单:
- [ ] 数据量和增长速度已评估
- [ ] 选定了同步方案(binlog / 双写 / ETL)
- [ ] 工具和代码已就绪
- [ ] 增量同步链路已建立并验证
- [ ] 全量同步方案已确定(按时间 / 按 ID 分批)
- [ ] 校验工具已开发完成
- [ ] 切流路由层就绪(可秒级切换)
- [ ] 监控大盘已搭建(同步延迟、一致性、业务指标)
- [ ] 回滚预案已演练
- [ ] 旧库下线计划已对齐(至少观察 30 天)
- [ ] 关联团队已对齐(业务 / 运维 / DBA)
- [ ] 容灾方案:迁移期间源库或目标库挂了怎么办
# 9.2 选型决策树
flowchart TD
Start([我要做数据同步/迁移]) --> Q1{是同步还是迁移?}
Q1 -->|持续同步| Q2{实时性要求?}
Q1 -->|一次性迁移| Q3{数据量级?}
Q2 -->|秒级| BinLog1[binlog 订阅<br/>Canal/Debezium]
Q2 -->|分钟级| ETL1[定时 ETL<br/>DataX]
Q2 -->|强一致| App[应用层双写或<br/>同步 RPC]
Q3 -->|< 1GB| Dump[mysqldump<br/>简单导入导出]
Q3 -->|1GB - 100GB| BinLog2[binlog + 全量<br/>Canal]
Q3 -->|100GB - 1TB| Cloud[云厂商 DTS<br/>或自建工具]
Q3 -->|> 1TB| Pro[专业方案<br/>分批 + 多通道]
BinLog1 --> Down{下游异构?}
Down -->|是| Kafka[+Kafka 多消费者]
Down -->|否| Direct[直接同步]
style Dump fill:#e3f2fd
style BinLog1 fill:#e8f5e8
style BinLog2 fill:#fff3e0
style Cloud fill:#f3e5f5
style Pro fill:#ffebee
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
最后一句话:数据同步迁移是信息系统最危险的工程之一——比新功能开发危险 10 倍,因为它涉及"已经存在的生产数据"。开篇那个 380 万条丢失只是冰山一角。
好的数据迁移 = 业务无感、数据无损、过程可控、终态可信。