2. CDC基础概念
变更数据捕获(Change Data Capture,CDC)是一类从源系统中识别、提取并向下游传递“数据变化”的技术统称。它的核心目标不是搬运全量数据,而是持续捕获插入、更新、删除这三类增量事件,并尽可能保留变化的顺序、事务边界与提交时间。理解CDC,关键要抓住三个词:变更(什么算变化)、捕获(从哪里、以什么代价拿到变化)、传递(如何保证下游按正确语义消费)。
在数据链路中,CDC通常位于“源系统”和“下游消费系统”之间,扮演增量供数管道的角色。它既可以是数据库日志的解析器,也可以是应用层双写产生的消息,还可以是周期性快照对比得出的差异集合。不同实现方式在实时性、侵入性、完整性和运维成本上差异巨大,这也是后续讨论重复、乱序与幂等治理的根源。
一、核心原理:变更从哪里来
无论哪种CDC实现,本质上都在回答同一个问题:如何把源端的“状态变化”转化为可传输的“事件流”。源系统的变化通常有三种可观测载体:
- 事务日志:数据库为保证持久性与复制而写入的WAL、binlog、redo log等,天然包含行级前后镜像与提交顺序,是最接近“真相”的变更来源。
- 触发器与时间戳:在表上挂触发器或依赖updated_at字段,把变化写入影子表;实现直接,但对源库有侵入,且容易遗漏硬删除。
- 快照对比:周期性拉取全量或分区数据,与上一版本比对得出差异;对源系统压力可控,但实时性差,且难以还原中间态。
捕获到变化后,CDC还需要完成事件建模:为每条变更附加操作类型、主键、前后镜像、源事务ID、提交时间戳与位点信息。这些元数据决定了下游能否做去重、排序与幂等写入。缺少位点或事务ID的事件流,几乎必然在故障恢复时产生重复或乱序。
二、方法流程:从捕获到可消费事件
一个可落地的CDC链路通常包含五个阶段,每个阶段都会影响最终数据质量:
- 识别变更源:确认源库是否开启binlog、日志格式是否为ROW、是否有主键与提交时间。以MySQL为例,需检查关键参数:
SHOW VARIABLES LIKE 'log_bin'; -- 必须为 ON
SHOW VARIABLES LIKE 'binlog_format'; -- 建议 ROW
SHOW VARIABLES LIKE 'binlog_row_image'; -- 建议 FULL,保留前后镜像
SHOW VARIABLES LIKE 'gtid_mode'; -- 建议 ON,便于位点恢复
- 抽取与解析:日志解析器按位点顺序读取日志,将行级变更还原为事件;触发器方案则从影子表轮询;快照对比按分区计算差异。
- 事件标准化:统一操作类型(I/U/D)、主键、前后镜像、事务ID、提交时间与源位点,形成下游可识别的契约。
- 传输与缓冲:通过Kafka、Pulsar等消息中间件承载事件流,利用分区键保证同一主键有序,利用位点提交支持断点续传。
- 下游消费:数仓做合并入湖,搜索做索引更新,缓存做失效,流计算做实时指标。消费端必须实现幂等与去重。
三、典型应用场景与数据流架构
CDC的典型场景可以归纳为四类:实时数仓入湖(ODS层增量同步)、缓存与搜索一致性(数据库变更驱动缓存失效与索引更新)、微服务数据同步(跨库异构数据复制)、审计与合规(保留完整变更历史)。这些场景对实时性和一致性的要求不同,但共享同一条数据流骨架。
| 实现方式 | 实时性 | 源库侵入 | 删除捕获 | 典型工具 | 主要风险 |
|---|---|---|---|---|---|
| 日志解析 | 秒级/毫秒级 | 低 | 支持 | Debezium、Canal、Flink CDC | 位点丢失导致重复或断流 |
| 触发器/时间戳 | 秒级 | 高 | 需额外设计 | 自研影子表、审计表 | 漏硬删除、源库性能下降 |
| 快照对比 | 小时级/天级 | 中 | 可推断 | DataX、Sqoop、自研Diff | 中间态丢失、差异误判 |
以日志解析为例,一条典型的变更事件在Kafka中可能呈现如下结构,其中offset与txid是后续去重和排序的关键字段:
{
"op": "U",
"table": "orders",
"key": {"order_id": 10086},
"before": {"status": "CREATED", "amount": 99.00},
"after": {"status": "PAID", "amount": 99.00},
"txid": "gtid:3f2a-11e9",
"ts_ms": 1718000000123,
"source": {"file": "mysql-bin.000042", "pos": 158734}
}
四、误区与复盘:CDC不是“银弹”
实践中常见的误区有三类。第一,把CDC当作全量同步工具:CDC只传增量,首次接入仍需快照初始化,且快照与增量切换点必须对齐,否则会丢数或重复。第二,忽略事务边界:一个事务包含多行变更,若下游逐条消费而不按事务聚合,可能读到“半个事务”的中间状态。第三,认为消息中间件能自动去重:Kafka的幂等生产者只保证单分区单会话不重复,跨会话、跨分区、消费端重放仍会产生重复。
理解CDC的定义、实现方式与数据流架构,是后续讨论重复、乱序与幂等治理的前提。只有先看清变化从哪里来、以什么形态流动、在哪些环节可能失真,才能有针对性地设计去重键、排序策略与幂等写入方案。