首页/数据/NoSQL 速查/CDC 与双写:把变更同步到派生存储
选型与架构50 条

CDC 与双写:把变更同步到派生存储

同步主库变更到搜索/缓存/分析库有两条主路:应用双写(实现直接,一致性靠补偿)与 CDC(消费 binlog/oplog/WAL 异步投递)。CDC 不侵入写路径且天然按变更顺序投递,代价是链路组件更多、重复投递要幂等消费。

NoSQL 速查数据更新于 2026-10-13

CDC 与双写是什么

把主库变更同步到搜索、缓存或分析库有两条主路:应用双写实现直接,但一致性要靠补偿与对账;CDC 消费 binlog、oplog 或 WAL 异步投递,不侵入写路径,且天然按变更顺序传递,代价是链路组件更多、重复投递需要幂等消费。选择逻辑大致是:无法改动写代码或变更量大时用 CDC;链路要极简、延迟要求高时双写加对账更直接。两者都建议保留全量重建能力。

基本信息

双写写主库成功后写派生存储,失败进补偿队列,靠定时对账兜底
CDC用 Debezium 等工具把 binlog、逻辑复制或 change stream 转成事件
消费要求至少一次投递,按主键做幂等 upsert,删除事件同样要传递
断点续传保存位点或 resume token,重启后从该点继续
所属系统数据同步架构通用方案

典型使用场景

  • 搜索副本:主库写完后把文档同步到搜索集群
  • 缓存刷新:数据变更后失效或更新缓存条目
  • 数仓入湖:把业务变更投递到分析侧做明细建模
  • 异构迁移:新库上线期间双跑并比对数据
  • 流处理与审计:把变更事件作为下游计算的输入

订阅变更(结构示意)

订阅变更(结构示意)
const cs = db.orders.watch();        // 订阅集合变更
while (cs.hasNext()) {
  const e = cs.next();
  upsertIntoSearch(e.documentKey._id, e.fullDocument);   // 幂等写入下游
  saveResumeToken(e._id);            // 保存位点,重启后从令牌继续
}

// 只订阅关心的变更类型
db.orders.watch([{ $match: { operationType: { $in: ["insert", "update", "replace"] } } }]);

两条路线对照

方式实现方式一致性
双写应用写主库后再写派生存储靠补偿与对账兜底
CDC解析日志异步投递顺序可靠,需幂等消费
时延双写通常更低CDC 取决于投递链路
侵入性需要改动业务代码不改写路径
运维成本组件少需要维护采集与消息链路
选择逻辑:无法改动写代码或变更量大时用 CDC;链路要极简、延迟要求高时双写加对账更直接。
双写不能假设两边同时成功,必须有补偿或对账,否则搜索库与主库会长期不一致。
CDC 消费者不做幂等时,重放与重复投递会造成重复计数或数据放大,幂等键通常用主键加版本号。

易错点

  • 双写不能假设两边同时成功,必须有补偿或对账,否则两边会长期不一致
  • CDC 消费者不做幂等时,重放与重复投递会造成重复计数或数据放大
  • 只同步插入与更新而漏掉删除事件,下游会残留已删除的数据
  • 没有全量重建能力:一旦下游数据漂移,修复成本极高

数据来源与参考

  • MongoDB Manual: Change streams
  • Debezium Documentation: Connectors
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌