3. 数据重复的根源
在CDC(Change Data Capture)治理中,重复数据几乎不是“偶发异常”,而是分布式链路在故障与恢复之间必然要面对的系统性现象。很多团队第一次遇到重复时,直觉是“下游去重没做好”,但真正的原因往往藏在采集、传输、消费、位点管理和并行处理等多个环节。理解重复的根源,是设计幂等与去重策略的前提。
一、核心原理:重复的本质是“至少一次”语义的代价
CDC链路通常由源端日志解析、消息传输、消费写入三段组成。为了保证不丢数据,绝大多数实现选择“至少一次”(at-least-once)投递语义:只要不能确认下游已成功处理,就允许重发。重复因此不是bug,而是可靠性设计的副产品。真正危险的是把“至少一次”误当成“恰好一次”,从而在业务侧缺少幂等保护。
从数据形态看,重复可分为三类:完全相同的记录重复、同一主键的多次变更重复、以及因位点回退导致的历史变更重放。三者的治理难度依次上升,因为后者可能改变最终状态。
二、五类常见原因逐层拆解
1. 源端重试。源库在事务提交、日志切换或连接中断时,采集组件可能重新读取同一段binlog或WAL。例如MySQL主从切换后,新主库的binlog位置与旧主库不一致,采集器若按旧位点重新拉取,就会把已发送的变更再发一次。这类重复通常表现为“同一事务ID或同一LSN区间重复出现”。
2. 网络抖动。生产者发送消息后未收到Broker的ACK,会触发重发;但Broker可能已经写入成功。Kafka、Pulsar、RocketMQ在开启重试时都可能产生这种“ACK丢失型重复”。其特点是重复记录时间戳接近、内容完全一致。
3. 消费位点回退。消费者处理完数据但位点提交失败,或人为重置offset到较早位置,会导致一批历史变更被重新消费。这是最容易被忽视、也最容易造成状态错乱的一类重复。位点回退不仅产生重复,还可能让旧值覆盖新值。
4. 多分区并行。同一业务主键若被分散到不同分区,多个消费者并行处理时无法保证全局顺序,可能出现“先到的后写、后到的先写”,在重试场景下进一步放大重复。分区键设计不当是根因之一。
5. 故障恢复。任务重启、扩缩容、Checkpoint恢复时,系统会从最近一次成功位点重新拉起。若Checkpoint与位点提交不是原子操作,就会出现“已处理但未记录”的区间被重放。
| 重复原因 | 典型触发条件 | 重复特征 | 优先治理手段 |
|---|---|---|---|
| 源端重试 | 主从切换、日志重读 | 同事务/LSN区间重复 | 源端位点校验、事务去重 |
| 网络抖动 | ACK丢失、发送超时 | 内容完全一致、时间接近 | 幂等生产者、消息ID去重 |
| 位点回退 | 提交失败、手动重置 | 历史变更重放 | 位点原子提交、回退告警 |
| 多分区并行 | 分区键分散、并发消费 | 乱序+重复交织 | 按主键分区、顺序消费 |
| 故障恢复 | 重启、扩缩容、Checkpoint | 区间性重放 | 精确一次Checkpoint、恢复校验 |
三、案例场景:一次位点回退引发的“数据倒灌”
某电商订单CDC链路中,消费者在处理“订单支付成功”事件后写入数仓,但位点提交因网络超时失败。任务重启后从旧位点恢复,重新消费了最近15分钟的变更。结果数仓中同一订单出现多条支付记录,且部分“已取消”状态被旧的“待支付”覆盖。排查日志可见:
2024-06-11 10:23:41 INFO Consumer offset commit failed, will retry
2024-06-11 10:23:46 WARN Commit retry exhausted, offset not advanced
2024-06-11 10:24:02 INFO Consumer restarted, seek to offset 88231
2024-06-11 10:24:03 INFO Re-consuming records from offset 88231 to 88990
复盘结论:位点提交与业务写入不在同一事务中,导致“写了但没记位点”。治理方案是引入幂等写入,以订单ID+变更版本号作为唯一键,并在恢复时校验位点与业务状态的一致性。
四、误区与复盘
- 误区一:认为开启“精确一次”就万事大吉。精确一次通常只在特定组件内部成立,跨系统端到端仍可能重复。
- 误区二:只在下游做distinct去重。distinct无法处理“同一主键不同版本”的重复,可能保留错误版本。
- 误区三:忽视位点提交的原子性。位点与业务写入分离,是故障恢复重复的最大来源。
- 误区四:分区键随意选择。同一主键跨分区会同时引入乱序与重复,治理成本成倍上升。
总结来看,CDC链路中的重复由源端重试、网络抖动、消费位点回退、多分区并行与故障恢复共同构成。它们不是孤立故障,而是“至少一次”语义、分布式状态管理与并发处理共同作用的结果。只有先定位重复的层次与触发机制,才能设计出既保不丢、又保不重的治理方案。