CDC 增量同步如何保证不丢不重?
面试怎么回答
CDC 不丢不重我会按端到端链路设计:源端保证日志和位点可恢复,消息层持久化并允许重放,目标端使用稳定事件 ID、业务主键和源版本做幂等 UPSERT 或事件去重。全量快照必须与增量日志边界衔接,避免中间漏数或旧快照覆盖新值。最后通过行数、唯一性、最大位点、删除和校验和对账验证,而不是只依赖 Exactly-Once 配置。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
CDC 任务重启后,目标表多了几条重复变更。有人第一反应是“Offset 没提交”,但即使正确提交位置,也无法只靠一个组件保证端到端 Exactly-Once。
生产里更现实的目标是:源端变更不遗漏,传输允许重放,目标端通过稳定事件身份和幂等写入消除重复,再用对账证明最终一致。
先把“不丢”和“不重”拆开
不丢依赖源数据库日志保留、采集位点、消息持久化和故障恢复。任务停太久,WAL 或 Binlog 已经被清理,即使程序能启动,也可能无法从旧位点继续。
不重则更难。采集器可能已经把事件写到下游,但还没来得及提交 Offset 就宕机;恢复后会再次发送这批事件。很多 CDC 系统因此采用 At-Least-Once:宁可重复,也不漏变更。

每条变更必须有稳定身份
可用于去重的字段通常来自源库位置和记录身份,例如 Binlog 文件与位置、GTID、LSN、事务 ID、表名、主键和事务内序号。
不要只用“主键”去重。某个订单可以先插入、再更新、再删除,它们主键相同却是三次合法变更。更不能只用处理时间,因为重放后的时间已经变化。
事件唯一键要满足:同一源事件重放时保持不变,不同变更不会误判为同一事件,并能支持排序或版本比较。
目标端怎么做到幂等?
如果目标保存当前状态,可以按业务主键 UPSERT,并比较源端版本或日志位点,只允许更新到更晚版本。这样同一事件写两次,最终状态不变;乱序旧事件也不会覆盖新状态。
如果目标保存完整变更流水,则用事件唯一键做唯一约束或去重表,保留每次合法操作。Delete 事件也必须进入模型,不能只处理 Insert 和 Update。
对于“写目标数据”和“提交消费位点”跨两个系统的情况,很难靠普通事务原子完成。可以用支持事务的 Sink,或采用幂等写入 + 重放;核心是失败后允许安全重试。
全量快照和增量如何衔接?

初次同步常见风险是:先做全量期间源表仍在变化。正确方案要记录快照对应的日志边界,并缓存或持续消费边界后的增量,快照完成后再按位点接续。
如果直接“全量结束后再启动 CDC”,中间窗口的变更可能丢失;如果全量和增量同时写却没有版本控制,旧快照又可能覆盖新变更。
最后必须做对账
Exactly-Once 配置不是验收结论。我会按表和时间窗口检查源目标行数、主键唯一性、删除数量、最大位点、延迟、字段校验和,并抽样回放 Insert—Update—Delete 链路。
还要监控日志保留余量、采集 Lag、消息积压、Sink 失败、重复事件率和死信。异常恢复后重新对账,才能证明没有形成静默数据缺口。