2. CDC基础概念

变更数据捕获(Change Data Capture,CDC)是一类从源系统中识别、提取并向下游传递“数据变化”的技术统称。它的核心目标不是搬运全量数据,而是持续捕获插入、更新、删除这三类增量事件,并尽可能保留变化的顺序、事务边界与提交时间。理解CDC,关键要抓住三个词:变更(什么算变化)、捕获(从哪里、以什么代价拿到变化)、传递(如何保证下游按正确语义消费)。

在数据链路中,CDC通常位于“源系统”和“下游消费系统”之间,扮演增量供数管道的角色。它既可以是数据库日志的解析器,也可以是应用层双写产生的消息,还可以是周期性快照对比得出的差异集合。不同实现方式在实时性、侵入性、完整性和运维成本上差异巨大,这也是后续讨论重复、乱序与幂等治理的根源。

一、核心原理:变更从哪里来

无论哪种CDC实现,本质上都在回答同一个问题:如何把源端的“状态变化”转化为可传输的“事件流”。源系统的变化通常有三种可观测载体:

  • 事务日志:数据库为保证持久性与复制而写入的WAL、binlog、redo log等,天然包含行级前后镜像与提交顺序,是最接近“真相”的变更来源。
  • 触发器与时间戳:在表上挂触发器或依赖updated_at字段,把变化写入影子表;实现直接,但对源库有侵入,且容易遗漏硬删除。
  • 快照对比:周期性拉取全量或分区数据,与上一版本比对得出差异;对源系统压力可控,但实时性差,且难以还原中间态。

捕获到变化后,CDC还需要完成事件建模:为每条变更附加操作类型、主键、前后镜像、源事务ID、提交时间戳与位点信息。这些元数据决定了下游能否做去重、排序与幂等写入。缺少位点或事务ID的事件流,几乎必然在故障恢复时产生重复或乱序。

CDC 三种实现方式与统一数据流 日志解析 binlog / WAL / redo 行级前后镜像 低侵入 · 高实时 触发器 / 时间戳 影子表 · updated_at 实现直接 · 有侵入 易漏硬删除 快照对比 全量 / 分区拉取 差异计算 低实时 · 难还原中间态 统一变更事件 op · key · before · after · txid · ts · offset 下游消费:数仓 / 搜索 / 缓存 / 流计算 / 审计 按位点消费 · 去重 · 排序 · 幂等写入

二、方法流程:从捕获到可消费事件

一个可落地的CDC链路通常包含五个阶段,每个阶段都会影响最终数据质量:

  1. 识别变更源:确认源库是否开启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,便于位点恢复
  1. 抽取与解析:日志解析器按位点顺序读取日志,将行级变更还原为事件;触发器方案则从影子表轮询;快照对比按分区计算差异。
  2. 事件标准化:统一操作类型(I/U/D)、主键、前后镜像、事务ID、提交时间与源位点,形成下游可识别的契约。
  3. 传输与缓冲:通过Kafka、Pulsar等消息中间件承载事件流,利用分区键保证同一主键有序,利用位点提交支持断点续传。
  4. 下游消费:数仓做合并入湖,搜索做索引更新,缓存做失效,流计算做实时指标。消费端必须实现幂等与去重。
关键认知:CDC只保证“变化被捕获并传递”,不自动保证“下游恰好一次”。重复、乱序、幂等是消费侧与链路协同治理的结果,而不是CDC工具的单点能力。

三、典型应用场景与数据流架构

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链路的正确性依赖“源位点 + 事务ID + 主键 + 提交时间”四要素。缺少任一要素,重复与乱序治理都会变得被动。设计阶段就应把这些元数据写入事件契约,而不是等到线上出现重复后再补。

理解CDC的定义、实现方式与数据流架构,是后续讨论重复、乱序与幂等治理的前提。只有先看清变化从哪里来、以什么形态流动、在哪些环节可能失真,才能有针对性地设计去重键、排序策略与幂等写入方案。