变更数据捕获(CDC)
当业务需要在两个异构系统之间保持数据同步时,你面临一个经典的技术选择:是定时轮询源表,还是在应用层双写?前者延迟高、资源浪费大,后者代码侵入性强、事务边界模糊。变更数据捕获(Change Data Capture,CDC) 提供了一种更优雅的解法:监听数据库本身的数据变更事件,将变更日志作为数据源。
核心原理
CDC 的本质是解析数据库的事务日志(Transaction Log / Write-Ahead Log)。每当一条数据被插入、更新或删除,数据库并不会立即修改磁盘上的数据页,而是先将变更记录写入事务日志。如果数据库崩溃,恢复时可以通过重放日志还原数据。正是这种设计启发了 CDC 的实现思路。
根据技术实现方式,CDC 可以分为三种类型:
基于日志(Log-based) 是最推荐的方式。MySQL 的 binlog、PostgreSQL 的 WAL、Oracle 的 Redo Log、MongoDB 的 Oplog 都属于这一类。当 CDC 工具连接到数据库并读取日志时,源库的应用完全不受影响,日志中的信息完整保留了操作类型(INSERT/UPDATE/DELETE)和变更前后的完整数据。
基于差异(Diff-based) 的实现方式更为简单粗暴:定期对源表进行全表扫描,与上一次的结果对比,找出差异行。这种方式实现成本低,但延迟高、资源消耗大,而且无法捕获 DELETE 操作(除非额外维护「软删除」标记)。
基于触发器(Trigger-based) 在源表上创建 INSERT/UPDATE/DELETE 触发器,将变更写入一张影子表,CDC 工具读取影子表获取变更。这种方式对源库性能有明显影响,且触发器的维护增加了数据库的复杂性。
Debezium + Kafka 的主流实现
在开源生态中,Debezium 是最成熟的 CDC 框架,它基于上述的日志解析方式,连接 MySQL、PostgreSQL、MongoDB 等数据库,将变更事件以统一格式发布到 Kafka。
Debezium 发出的每条消息包含 before 和 after 两个快照、op 操作类型(c=create, u=update, d=delete, r=snapshot)以及变更发生的时间戳和来源数据库信息。这种格式足以支持下游消费者实现任意复杂的数据同步逻辑。
应用场景
数据库到数据湖的实时同步是最常见的 CDC 应用。传统的 ETL 方案是定期执行批量抽取,数据往往滞后数小时甚至数天。使用 CDC 后,数据从写入源库到出现在数据湖中的延迟可以控制在秒级,这对于实时报表、实时风控等场景至关重要。
微服务间的数据共享是另一个重要场景。在微服务架构中,每个服务通常拥有独立的数据存储,但某些场景下又需要访问其他服务的数据。CDC 可以优雅地实现跨服务的数据同步,而无需服务间直接建立数据库连接(这是微服务架构的大忌)。
事件驱动架构的入口让 CDC 的价值进一步放大。当订单状态变更、用户信息更新等业务事件被捕获后,可以触发下游的营销自动化、审计日志生成、缓存失效等操作,整个系统从「请求-响应」模式演进到「事件-响应」模式,解耦程度更高。
挑战与最佳实践
CDC 并非银弹,使用时需要关注几个问题。
事务边界破碎是 CDC 带来的根本性挑战。源库中的一次事务可能涉及多张表的变更,这些变更在日志中是分散记录的,下游消费者需要自己实现「将同一次事务的多个变更聚合处理」的逻辑。Debezium 通过 transaction_id 字段提供事务边界信息,但下游消费逻辑需要正确处理。
Schema 变更是另一个痛点。当源表新增字段或修改字段类型时,CDC 工具可能无法正确解析历史日志。需要在 Schema Registry 中记录版本演进历史,下游消费者根据版本信息做相应的数据转换。
顺序性保证在分布式场景下难以实现。Kafka 分区间的消息是并行处理的,如果业务对变更顺序敏感,需要将同一主键的所有变更路由到同一分区,这通常意味着消息 key 要设置为表的主键。