增量数据采集:CDC技术原理与工具选型

数据湖仓的增量处理,第一步就是「怎么把变化的数据捞出来」。

CDC(Change Data Capture,变更数据捕获)就是干这个的。说白了,它能让下游实时感知到数据库里哪行数据被改了、被删了、被插入了。没有CDC,你只能全量跑批,一天一次,延迟高得吓人。

我最早做数仓时,用的还是时间戳+定时扫描的方式。后来接触了日志解析,才发现以前走了不少弯路。今天我把三种主流CDC原理和几个常用工具掰开揉碎讲清楚。

核心观点:CDC不是银弹,选型要看业务对延迟、一致性、侵入性的容忍度。日志解析型最优雅,但复杂度也最高。

三种CDC原理:日志解析、时间戳、触发器

这三种方式,我按「推荐程度」从高到低来讲。

1. 日志解析(Log-based CDC)

这是目前最主流的方式。数据库的每次写入、更新、删除,都会记录在事务日志里(比如MySQL的binlog、PostgreSQL的WAL、Oracle的Redo Log)。

CDC工具直接读取这些日志,解析出变更事件,然后推送给下游。

优点很明显:

  • 对业务代码零侵入——你不需要改表结构、加字段
  • 延迟极低——日志一写,立刻就能读到
  • 能捕获所有变更,包括删除操作

缺点呢?

  • 日志格式各数据库不同,解析逻辑复杂
  • 日志有保留期限,如果消费不及时可能丢失数据
  • 需要数据库开启相关配置(比如MySQL要开binlog且设为ROW模式)

我的经验:我在一个金融项目中,就因为binlog保留时间只设了24小时,结果下游Kafka消费集群挂了半天,恢复后日志已经过期。从那以后,我习惯把binlog保留期至少设到72小时,并且加监控报警。

2. 时间戳(Timestamp-based CDC)

这是最朴素的方式。在源表上加一个last_modified或update_time字段,每次更新时由业务代码或数据库触发器更新这个字段。下游定时扫描:WHERE update_time > last_check_time。

优点:

  • 实现简单,几乎任何数据库都能做
  • 不需要额外配置日志

缺点:

  • 无法捕获删除操作(除非用软删除)
  • 时间精度问题——同一毫秒内的多条变更可能漏掉
  • 对业务代码有侵入,必须保证每个写操作都更新时间戳
  • 扫描频率决定了延迟,频率高了又影响数据库性能

注意:时间戳方式在分布式系统里尤其容易出问题。各服务器时钟不同步,可能导致数据漏采或重复。我曾经踩过这个坑,后来强制所有应用服务器用NTP同步,并且把时间戳字段改成数据库服务器本地时间。

3. 触发器(Trigger-based CDC)

在源表上创建触发器(INSERT/UPDATE/DELETE),当数据变更时,触发器把变更记录写入一张专门的日志表。CDC工具再读取这张日志表。

优点:

  • 能捕获所有变更,包括删除
  • 不依赖数据库日志格式

缺点:

  • 对源库性能影响大——每次DML操作都要额外写一次日志表
  • 触发器逻辑维护成本高,改表结构时容易出问题
  • 如果日志表没有清理机制,会无限膨胀

我个人不太推荐触发器方式,除非你用的是那种不支持日志解析的老旧数据库,而且数据量很小。

三种原理对比

维度 日志解析 时间戳 触发器
业务侵入性 无 中(需加字段) 低(需加触发器)
延迟 秒级/毫秒级 取决于扫描间隔 秒级
捕获删除 支持 不支持(需软删除) 支持
数据库性能影响 低 中(扫描压力) 高
实现复杂度 高 低 中
典型场景 实时数仓、数据湖 离线批处理、小数据量 老旧数据库、无日志场景

常见CDC工具对比:Debezium、Canal、Maxwell

工具选型,我建议先看你的技术栈。Java生态用Canal或Debezium都行,但如果你已经在用Kafka,Debezium几乎是首选。

1. Debezium

这是目前最火的CDC工具,基于Kafka Connect架构。它支持MySQL、PostgreSQL、MongoDB、Oracle、SQL Server等多种数据库。

核心特点:

  • 原生集成Kafka,变更事件直接写入Kafka Topic
  • 支持快照+增量,首次启动会做一次全量快照,然后无缝切换到增量
  • 提供变更历史Topic,可以回溯任意时间点的数据状态
  • 社区活跃,文档完善

我去年帮一家电商公司搭实时数仓,用的就是Debezium + Kafka + Flink。MySQL的binlog变更,秒级就能进入Kafka,然后Flink做ETL写入Iceberg表。整个链路延迟不到3秒。

Debezium配置示例(MySQL Connector):

{
  "name": "inventory-connector",
  "config": {
    "connector.class": "io.debezium.connector.mysql.MySqlConnector",
    "database.hostname": "192.168.1.100",
    "database.port": "3306",
    "database.user": "debezium",
    "database.password": "dbz_pass",
    "database.server.id": "184054",
    "database.server.name": "dbserver1",
    "database.include.list": "inventory",
    "table.include.list": "inventory.orders,inventory.customers",
    "database.history.kafka.bootstrap.servers": "kafka:9092",
    "database.history.kafka.topic": "schema-changes.inventory",
    "include.schema.changes": "true",
    "snapshot.mode": "initial"
  }
}

2. Canal

Canal是阿里巴巴开源的,专门针对MySQL binlog解析。它伪装成MySQL的从库,向主库请求binlog。

核心特点:

  • 纯Java实现,部署简单
  • 支持直接投递到Kafka、RocketMQ、或者TCP直连
  • 阿里巴巴内部大规模验证过,稳定性高
  • 但只支持MySQL(以及兼容MySQL协议的数据库)

适用场景:如果你的数据源只有MySQL,而且团队对Java生态熟悉,Canal是个轻量好用的选择。

避坑指南:我曾经遇到Canal在解析大事务binlog时内存飙升的问题。原因是Canal默认会把整个事务的binlog都加载到内存。解决方案是调整canal.instance.memory.buffer.size和canal.instance.memory.buffer.memunit参数,或者开启transaction.flat模式让Canal逐条解析。

3. Maxwell

Maxwell也是一个MySQL CDC工具,由Zendesk开源。它的设计理念是「简单」——输出格式就是JSON,直接写入Kafka、Kinesis、RabbitMQ等。

核心特点:

  • 输出格式极其简洁,就是一个JSON字符串
  • 支持bootstrap模式做全量同步
  • 支持分区键自定义
  • 同样只支持MySQL

Maxwell输出示例:

{
  "database": "inventory",
  "table": "orders",
  "type": "insert",
  "ts": 1696000000,
  "data": {
    "id": 1001,
    "customer_id": 500,
    "total": 299.00,
    "status": "pending"
  }
}

Maxwell的优点是轻量,一个JAR包就能跑。但功能上比Debezium弱一些,比如不支持schema evolution的细粒度控制。

三款工具横向对比

维度 Debezium Canal Maxwell
支持数据库 MySQL、PG、MongoDB、Oracle、SQL Server等 MySQL MySQL
架构 Kafka Connect 独立Server 独立Server
输出目标 Kafka(原生) Kafka、RocketMQ、TCP Kafka、Kinesis、RabbitMQ、文件
全量快照 支持(snapshot mode) 需额外开发 支持(bootstrap)
Schema变更 支持(schema history topic) 有限支持 有限支持
社区活跃度 高 中 低
学习曲线 中(需理解Kafka Connect) 低 低

CDC在数据湖仓中的整体流程

下面这张图,是我在实际项目中常用的CDC数据流向。你可以看到,从源库到数据湖,中间经过了日志采集、消息队列、流处理、湖存储几个环节。

CDC数据流向图 CDC数据流向图 源数据库 MySQL / PG / Oracle Binlog / WAL CDC工具 Debezium / Canal / Maxwell 消息队列 Kafka / Pulsar 流处理引擎 Flink / Spark Streaming 数据湖存储 Iceberg / Hudi / Delta 关键说明: 1. CDC工具伪装成从库读取binlog 2. 变更事件以JSON/Avro格式写入Kafka 3. 流处理引擎做ETL后写入湖存储

选型建议

说了这么多,到底怎么选?我给出几个实际场景的推荐:

  • 场景一:多数据库源(MySQL + PG + MongoDB),已有Kafka集群 → Debezium
  • 场景二:纯MySQL,团队Java技术栈,不想引入Kafka Connect → Canal
  • 场景三:纯MySQL,数据量不大,想要极简部署 → Maxwell
  • 场景四:老旧数据库不支持日志解析 → 考虑时间戳或触发器方案

最后提醒一句:不管选哪个工具,一定要在生产环境做好监控。CDC链路一旦断了,数据延迟会越积越大。我习惯给每个CDC Connector加一个延迟监控,超过5分钟就报警。别问我为什么——经历过一次48小时的数据断层,你就懂了。