CDC 变更数据捕获:实时数据同步
每次改数据手动触发下游同步?CDC 自动捕获数据库变更,实时推给下游(搜索/缓存/数仓)。本文讲清 CDC 的原理(读 WAL 日志)、工具(Debezium)、适用场景(同步 ES/缓存/数仓/微服务事件)、以及它比"双写/定时轮询"强在哪。
本文要点
- 主题聚焦:DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
CDC 变更数据捕获:实时数据同步
一个很常见的场景:你有一张 orders 表写在 Postgres 里,但搜索要用 Elasticsearch,缓存要用 Redis,数仓要进 ClickHouse,还要给履约微服务发个事件。
每次写库之后,你怎么办?
最直觉的写法是——在业务代码里"双写":先 INSERT INTO orders ...,再 index into es,再 XADD 到 Redis Stream,再发个 Kafka 消息。看起来清晰,实际上是个定时炸弹。本文讲清楚为什么 CDC(Change Data Capture,变更数据捕获)是这类问题的更优解,以及它到底怎么工作、什么时候值得上。
一、CDC 解决什么:让数据库变更"自动"流向下游
CDC 的核心一句话:数据库自己知道每次变更,我们把它捕获下来,推给所有需要的下游。
不再由应用代码负责"通知"每个下游,而是有一个组件趴在数据库旁边,把 insert / update / delete 事件实时拎出来,广播出去。下游谁需要谁订阅。
┌──────────┐
│ Postgres │ (WAL 日志在不断写入)
└────┬─────┘
│ CDC 组件读取日志
▼
┌──────────┐
│ Kafka │ (变更事件流)
└──┬─┬─┬───┘
┌────────┘ │ └────────┐
▼ ▼ ▼
┌──────┐ ┌────────┐ ┌────────┐
│ ES │ │ 数仓 │ │ 微服务 │
└──────┘ └────────┘ └────────┘
数据库变成了整个系统唯一的真实数据源(single source of truth),所有下游都从它派生,且派生过程是自动、实时、一致的。
二、原理:读事务日志,而不是去查表
CDC 最关键的一点是它怎么拿到变更。有两种截然不同的路子,质量差一个数量级。
差的:基于查询(query-based)。定时跑 SELECT * FROM orders WHERE updated_at > ?,对比前后差异。这是"伪 CDC",本质是轮询。
好的:基于日志(log-based)。数据库为了自身崩溃恢复,本来就在持续写一份事务日志——Postgres 叫 WAL(Write-Ahead Log),MySQL 叫 binlog。这份日志里完整记录了每一行数据的变更。CDC 组件假装自己是个"备库",从这份日志里解析出结构化的变更事件。
Postgres WAL 片段(简化):
BEGIN tx=12345
UPDATE public.orders SET status='paid' WHERE id=99
COMMIT tx=12345
CDC 解析后发出的事件(简化):
{
"op": "u", // update
"before": {"id":99,"status":"pending"},
"after": {"id":99,"status":"paid"},
"source": {"tx":12345, ...},
"ts_ms": 1718000000000
}
为什么 log-based 完胜?三个原因:
- 不漏:日志记录了每一次提交,包括同事务里的多次变更,你能拿到完整历史。
- 不扰:不需要对业务表加锁、不需要建触发器、不需要扫表,对线上库几乎零侵入。
- 顺序保证:日志是按提交顺序写的,下游按顺序消费就是数据的真实演进顺序。
三、工具:Debezium + Kafka 是事实标准,PG 也有原生玩法
Debezium 是目前最主流的开源 CDC 项目,它基于 Kafka Connect 框架运行,为 PG / MySQL / MongoDB / SQL Server 等都提供了 Source Connector。架构是固定的三段式:
[ PG / MySQL ] --Debezium Source--> [ Kafka topic ] --消费--> [ 下游 / Sink ]
(名为 pg.public.orders 的 topic)
PG 那一侧需要先开逻辑复制:把 wal_level 设成 logical,建一个 replication slot,Debezium 通过这个 slot 订阅 WAL 的变更流,转成标准的 Kafka 消息发出去。下游可以再接一个 JDBC Sink Connector 写回 ES/ClickHouse,也可以让微服务自己写消费者。
如果嫌 Kafka Connect 那一套太重,PG 生态里还有更轻的选择:原生 logical replication(CREATE PUBLICATION / SUBSCRIPTION),适合 PG→PG 的同步;或者 pglogical 插件。也可以用 Debezium Server(轻量版)直接发到 Pulsar / Kinesis / Redis Stream,跳过 Kafka。
非 Debezium 路线还有 Canal(阿里,MySQL binlog)、Maxwell(MySQL→Kafka,极简)。但如果你要支持多数据库类型、要有完善的 schema 演进处理,Debezium 仍是首选。
四、比双写强在哪:一致性这道坎,双写永远过不去
双写的代码通常长这样:
db.execute("INSERT INTO orders ...")
es.index("orders", body=...) # 万一这里挂了?
redis.set(cache_key, ...)
这里有三个绕不开的坑:
- 部分失败:DB 写成功、ES 写失败,数据就不一致了。加重试?加事务?跨系统根本没法做事务。加补偿?代码复杂度爆炸。
- 顺序错乱:并发下,A 的 DB 写成功了但 ES 还没刷新,B 先把 ES 写了——下游看到的是乱序的最终态,不是真实演进。
- 代码散落:每个写库的地方都要记得同步写下游,新人加一个接口忘写了,就静默不一致。
CDC 把这个责任从"每个写库的业务代码"挪到了"数据库日志"这一个点。应用只管写库,变更会自动且准确地流到下游——因为下游消费的就是数据库真实提交的内容,顺序就是提交顺序,不存在"一个成功一个失败"。一致性是结构性保证,不是靠程序员记性。
五、比轮询强在哪:延迟和负载,都是还不清的债
轮询(sync 脚本每分钟跑一次)看起来简单,实际两个指标都很差:
- 延迟:分钟级起步。用户改了资料,搜索结果要等下一轮才更新,体验割裂。
- 负载:每次
SELECT *全表扫一遍updated_at > now() - interval,表一大就是灾难;没有合适的索引更是直接打爆 DB。
CDC 是事件驱动:有变更才推,没变更零开销;延迟通常是亚秒级(从提交到下游可见,几百毫秒到几秒)。对线上库的压力来自读 WAL,而不是扫表,量级完全不同。
一句话:轮询是用 DB 的算力换简单,CDC 是用一套中间件换实时和低负载。
六、适用场景:CDC 真正发光的地方
实战里 CDC 最常见、最值得的四个场景:
- 同步搜索索引(Elasticsearch / OpenSearch)。DB 是结构化真实源,ES 专门干全文检索——用 CDC 把变更实时灌进 ES,搜索体验丝滑,且天然解耦。
- 维护缓存一致性。Redis 缓存 + DB 是经典难题(双删、延迟双删都不优雅)。用 CDC 监听变更、精准失效对应 key,比任何应用层套路都干净。
- 数据仓库 / ETL 入仓。把 OLTP 库的变更实时推到 ClickHouse / Snowflake / 数仓,做实时报表,告别 T+1 的离线批处理。
- 微服务事件驱动。订单服务改了状态,履约、风控、通知服务都要感知——与其在订单服务里手动
publish,不如让 CDC 把"订单表变更"直接变成事件流,这才是真正的"集成事件"而非"领域事件被滥用"。
一个判断标准:只要"数据库状态变化"本身就是一个有意义的业务事件,就该用 CDC。
七、代价:别假装它免费
CDC 不是银弹,代价实打实:
- 运维复杂度陡增。你要维护 Debezium + Kafka Connect + Kafka 一整套,监控 lag、处理 rebalance、给 Kafka 留够磁盘。对小团队是实打实的负担。
- Schema 变更(DDL)是头号雷区。给表加列、改类型、重命名——CDC 组件可能不认识新 schema,导致流中断或事件解析错。Debezium 有 schema registry 配合,但必须配套
CREATE TYPE之类的谨慎操作,上线前要演练。 - 顺序与分区矛盾。同一张表要保序就得单分区,单分区又卡吞吐;按主键分区能并发但要自己保证同一主键的事件进同一分区。这是设计期就要想清楚的。
- 全量 + 增量切换(snapshot)。首次启动要把存量数据先搬一遍,再切到增量。大表 snapshot 期间压力不小,要挑窗口。
小结
- CDC 的本质:从数据库事务日志(WAL/binlog)捕获变更,事件驱动地推给下游,而非应用双写或定时轮询。
- 比双写强:一致性是结构性保证,不靠程序员记性;应用代码也回到了"只管写库"的纯粹状态。
- 比轮询强:亚秒级延迟 + 低侵入负载,而不是分钟级延迟 + 全表扫描。
- 主流工具栈:Debezium + Kafka Connect + Kafka,PG 生态也有原生 logical replication / pglogical 这类轻量方案。
- 代价是实打实的:一套中间件的运维、schema 变更的雷、顺序与吞吐的权衡。
一句话:当"数据库的变更"本身就是业务事件,且下游不止一个时,CDC 是最干净的解;否则双写或轮询就够,别为了 CDC 而 CDC。
Related
相关推荐
文章
Docker 部署 Next.js 社区项目的注意事项
围绕 DevOps、Next.js 的技术趋势、工程实践和独立开发视角。
文章
OpenTelemetry 如何帮助你观察 AI 应用?
围绕 DevOps、AI 的技术趋势、工程实践和独立开发视角。
文章
长运行工具调用:Operation、Poll 与 Cancel 的终态协议
初始请求超时后,工具可能仍在服务端运行;发出取消也不等于副作用已停止。本文为单次长运行工具调用建立 operation 身份、轮询水位、取消竞态和唯一终态接受点。
文章
Agent 记忆删除传播:从用户遗忘请求到可验证清除
删除主表一行不会清掉向量、摘要、缓存和恢复后的备份。本文用删除状态机、代际墓碑、派生血缘与端到端 canary 验证真正遗忘。