E4 - 集成老系统:CDC + 妥协策略
不改旧系统就能把数据库变更转成事件——Change Data Capture(CDC)是事件驱动与传统系统集成的桥梁。
一句话
CDC(Change Data Capture)= 读取数据库 binlog/WAL → 生成事件流——不动旧系统业务代码、不破坏 SSOT 原则、不依赖双写的脆弱事务边界。
这本书在这一层说了什么
本层融合 EDM Ch 4(Data Liberation,56KB / 8749 词)+ Ch 10(Integration with Legacy,13KB / 2115 词)。
Bellemare Ch 4 关键论断:
"Legacy systems can be both extremely important to the organization and prohibitively difficult to refactor... Despite the complexity of a system, their internal data will still need to be accessed by other new systems."
核心论点 4 个:
- Data Liberation — 数据解放的概念
- Compromises for Data Liberation — 集成老系统的现实限制
- Converting Liberated Data to Events — 解放数据转事件
- CDC 实现方式(Ch 10 补充)
是什么
1. Data Liberation(数据解放)
Bellemare 关键概念:
"A stream of liberated events must materialize back into an exact replica of the source table."
Data Liberation = 从外部数据源(数据库)持续提取变更 → 转为事件流。
关键不变量:
- 解放事件流必须能物化回源表(精确副本)
- 源表与事件流最终一致(有延迟)
2. 为什么必须用 CDC 集成老系统?
Bellemare 列出 3 大现实障碍:
| 障碍 | 描述 |
|---|---|
| Limited developer support | 老系统开发支持少,需要低工作量方案 |
| Expense of refactoring | 重构老系统成本极高(尤其复杂单体) |
| Legacy support risk | 改老系统可能产生意外后果(技术债 + 隐藏连接) |
Bellemare 关键洞察:
"There is an opportunity for compromise here. You can use data liberation patterns to extract the data out of the data store and create the necessary event streams."
妥协策略:不改老系统代码 → CDC 提取数据 → 生成事件流 → 新服务消费。
3. 理想 vs 现实
理想状态:
事件流(Broker)= SSOT
↓ 物化
所有服务(包含老系统)现实(妥协):
老系统(不改) → CDC → 事件流(Broker)→ 新服务(消费)
↑
不读回(单向)Bellemare 关键论断:
"The event stream will be eventually consistent with the internal data set of the legacy application."
关键:老系统不订阅自己的解放事件——单向数据流。
4. CDC 与业务事件的关系
Bellemare 关键论断:
"The event stream will be eventually consistent with the internal data set of the legacy application... the goal is to keep the internal data set synchronized with the external event stream through strictly controlled publishing of event data."
CDC 事件 vs 业务事件:
| 维度 | CDC 事件 | 业务事件 |
|---|---|---|
| 来源 | 数据库 binlog/WAL | 业务代码显式发布 |
| 触发逻辑 | 数据库变更 | 业务事实 |
| 延迟 | 毫秒级(取决于 binlog flush) | 即时 |
| 业务语义 | 弱(行级变更) | 强(业务事件) |
| Schema | 隐式(库表结构) | 显式(Avro/Proto) |
5. Liberated Data 转事件的要求
Bellemare 关键论断:
"Liberated data, much like any other event, is subject to the same recommendations of schematization that were introduced in Chapter 3."
关键原则:
- 必须用 Schema Registry
- 必须有兼容性约束
- 必须 Full Compatibility 起步
Bellemare 关键提醒:
"Use the same standard format for both liberated event data and native event data across your organization."
关键:解放事件 + 原生事件 = 同一套 Schema 标准。
6. Ch 10 集成老系统的具体策略
Bellemare Ch 10 关键策略:
集成老系统的 3 大策略:
1. CDC(推荐,不改老系统)
2. 双写(短期可行,长期危险)
3. 老系统改造(成本最高,效益最深)CDC 的实现方式(来自 Ch 10 案例):
方式 1:基于 Binlog(MySQL / PostgreSQL)
- Debezium(开源,功能最全)
- Maxwell(轻量)
- AWS DMS(托管)
方式 2:基于 Trigger
- 老系统加 Trigger → 写 outbox 表
- 后台进程读 outbox → 发事件
方式 3:基于查询(轮询)
- 定期查询变更
- 简单但延迟大为什么
为什么 CDC 不破坏 SSOT 原则?
SSOT 关键概念:事件流是单一事实源(SSOT),所有服务从事件流物化状态。
反例(双写):
- 订单服务在事务中"先写订单表 + 再发 Kafka 消息" → 事务回滚但 Kafka 已发 → 数据漂移
- 违反 SSOT——业务代码在"伪造"事件
CDC:
- 老系统只写数据库(不变)
- CDC 工具读取 binlog → 生成事件
- 不依赖业务代码——事件来自数据库的事后观察
根因:CDC 把"事件生成"从事务中剥离到"事务后观察"——既保留老系统,又产生事件。
为什么老系统不读回解放事件?
Bellemare 关键论断:
"This is a form of unidirectional event-driven architecture, as the legacy system will not be reading back from the liberated event stream."
根因 1:避免无限循环
- 老系统写 DB → CDC 生成事件 → 老系统读事件 → 写 DB → ...
根因 2:老系统不需要
- 老系统的当前状态就是 DB 的状态
- 读解放事件 = 冗余
根因 3:单向是过渡
- 老系统最终会被替换为事件驱动微服务
- 过渡期间保持单向 = 简化
为什么 CDC Schema 必须显式?
Bellemare 关键论断:
"Failing to use an explicitly defined schema for liberated data will force downstream consumers to resolve any incompatibilities. This is extremely problematic for the provision of the single source of truth."
根因:
- 数据库 schema 隐含在 DDL
- 下游消费者无法"问"数据库
- 必须显式定义 + 版本化
怎么做
Step 1:识别集成需求
操作:
1. 列出所有老系统
2. 每个老系统问"新服务需要它的数据吗?"
3. 是 → 标记为 CDC 候选
产出:
- 老系统清单(带数据需求)Step 2:选择 CDC 实现方式
决策树:
Q1: 老系统支持 binlog / WAL 吗?
是 → 基于 Binlog 的 CDC(Debezium / Maxwell)
否 ↓
Q2: 允许加 Trigger 吗?
是 → 基于 Trigger(outbox 表)
否 ↓
Q3: 数据延迟可接受吗?
是 → 轮询查询
否 → 重新评估是否集成Step 3:部署 Debezium(MySQL 例)
# 注册 Debezium MySQL Connector
curl -X POST http://debezium-connect:8083/connectors \
-H "Content-Type: application/json" \
-d '{
"name": "mysql-orders-connector",
"config": {
"connector.class": "io.debezium.connector.mysql.MySqlConnector",
"database.hostname": "mysql",
"database.port": "3306",
"database.user": "debezium",
"database.password": "secret",
"database.server.id": "1",
"database.server.name": "mysql",
"database.whitelist": "orders_db",
"table.whitelist": "orders_db.orders"
}
}'Step 4:定义 Schema(CDC 事件专属)
// orders_cdc.proto
// Triggering Logic: emitted by CDC when orders table changes
message OrderCDC {
string order_id = 1;
string customer_id = 2;
string status = 3;
google.protobuf.Timestamp updated_at = 4;
// CDC 操作类型
string op = 5; // 'c'=create, 'u'=update, 'd'=delete
}Step 5:新服务消费 CDC 事件
from kafka import KafkaConsumer
import json
consumer = KafkaConsumer(
'mysql.orders_db.orders',
bootstrap_servers='kafka:9092',
group_id='order-event-consumer',
auto_offset_reset='earliest'
)
for message in consumer:
event = json.loads(message.value)
# event.op: 'c'=create, 'u'=update, 'd'=delete
# event.after: 新行数据
# event.before: 旧行数据
publish_to_event_stream(event)Step 6:渐进替换老系统
迁移路径:
阶段 1:CDC 提取老系统数据 → 新服务消费
阶段 2:新服务实现部分业务逻辑(与老系统并行)
阶段 3:流量切换到新服务
阶段 4:老系统退役什么时候用 / 什么时候不用
用 CDC 的 2 大场景
| 场景 | 关键判断 |
|---|---|
| 老系统迁移到事件驱动 | 不能改老代码 → CDC 桥接 |
| 多系统数据汇总 | 各系统数据库 → CDC → 统一事件流 → 数据仓库 |
不用的 2 大场景
| 场景 | 原因 | 替代 |
|---|---|---|
| 新建系统 | 事件溯源更彻底 | 事件优先 / 事件溯源 |
| 强一致 + 零延迟必要 | CDC 毫秒级延迟不可接受 | 同步 RPC |
看不见的地方
失效模式 1:CDC 延迟被低估
症状:业务假设"事件即时产生"但实际有 100-500ms 延迟 → 跨服务测试偶发不一致。
根因:CDC 是"事后观察",binlog 写入 + 解析 + 网络传输有延迟。
检测:监控 CDC 端到端延迟(P99 > 1s = 异常)。
修复:业务容忍度评估(秒级可接受 / 毫秒级需优化)。
失效模式 2:DDL 变更未同步
症状:老系统加字段 → CDC 事件结构变 → 消费方崩溃。
根因:DDL 变更未通知 CDC 工具 → schema 不匹配。
修复:DDL 变更必须走"Schema 变更流程"(参考 E3 演化规则)。
失效模式 3:CDC 工具单点故障
症状:Debezium 挂了 → 事件流停止 → 新服务拿不到最新数据。
根因:CDC 是关键路径但未做高可用。
修复:CDC 多副本 + binlog offset 持久化 + 故障自动恢复。
失效模式 4:双向数据流循环
症状:老系统读解放事件 → 写 DB → CDC 又生成事件 → 无限循环。
根因:违反 Bellemare 的"单向"原则。
修复:老系统绝对不订阅自己的解放事件。
失效模式 5:CDC 事件 vs 业务事件混用
症状:CDC 事件被当业务事件处理 → 业务语义丢失 → Bug。
根因:未区分两类事件的语义。
修复:CDC 事件加前缀(如 cdc.orders.created)或独立 topic。
失效模式 6:老系统退役失败
症状:新服务上线后老系统仍在运行 → 双写数据不一致。
根因:未明确老系统退役时间表。
修复:分阶段退役(read-only → 归档 → 退役)。
相关节点
骨架 A — 世界观
| 节点 | 关联 | 解释 |
|---|---|---|
| wonder-05-concealment | 强 | CDC = "揭开隐匿"——数据库的"暗箱操作"被 CDC 照亮成事件 |
| wonder-04-encounter | 中 | CDC 让老系统"照面"事件流——打破数据孤岛 |
骨架 B — 工程实践
| 节点 | 关联 | 解释 |
|---|---|---|
| change-management | 强 | CDC = 渐进变更——不改旧系统也能接入事件流 |
| feedback-mechanisms | 中 | CDC = 反馈机制——数据库变更是系统的"自我观察" |
与 IDDD 对位
| IDDD | EDM E4 |
|---|---|
| Ch 13 ACL | E4 CDC = 数据级 ACL(保护老系统不暴露内部 schema) |
状态
E4 概念递进落地完成(融合原书 ch04 56KB + ch10 13KB)。
- 4 个核心论点(Data Liberation / 现实限制 / Schema 要求 / CDC 实现)
- 6 步怎么做(含完整 Debezium 配置示例)
- 6 个失效模式
- 骨架 A/B + IDDD 完整映射