订阅更新

Spark foreachBatch 怎么保证幂等?有 Checkpoint 还会重复写吗?

面试问 245 约 4 分钟读完

本文目录
  1. 面试怎么回答
  2. Checkpoint 管进度,不接管任意外部副作用
  3. 先选业务幂等,还是批次幂等
  4. 完成标记必须与完整效果绑定
  5. 一个典型场景:账本有了,汇总仍然翻倍
  6. 写两个系统,不会自动形成一个事务
  7. 用故障窗口来验收,而不是只跑通一次
  8. 参考资料

面试怎么回答

有 Checkpoint 仍可能重复调用 foreachBatch,因为外部提交与 Spark 批次完成之间存在故障窗口。我会优先用稳定事件键或实体版本做幂等;需要批次级保证时,用管道身份加 batchId,并把目标合并与完成标记放在同一事务边界。还要覆盖部分分区成功和多目标失败,不能只测正常重跑。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

“已经配置 Checkpoint,所以写数据库不会重复。”如果面试官紧接着问:数据库提交成功后,Driver 在记录批次完成前退出,会发生什么?这时就需要把 Spark 的恢复进度与外部数据库的提交状态拆开看。

foreachBatch 的外部写入默认至少一次;要用稳定业务键或批次提交协议实现幂等,并处理部分成功和多目标重试。

Checkpoint 管进度,不接管任意外部副作用

foreachBatch 把每个微批次的 DataFrame 和 batchId 交给用户函数。Spark 可以根据 Checkpoint 恢复处理,但无法自动知道你在函数里调用的数据库、HTTP 接口或其他系统是否已经产生副作用。

因此它默认提供至少一次写入语义。某个批次可能重放,外部系统的效果是否重复取决于写入实现。这里讨论微批处理,foreachBatch 不适用于 continuous processing 模式;具体 API 支持应按部署版本核对。

先选业务幂等,还是批次幂等

业务幂等适合每条记录有稳定事件标识或实体版本的情况。插入事件表可使用唯一事件键;更新实体快照可使用完整业务主键,并防止旧版本覆盖新版本。简单把金额写成 amount = amount + delta,不会因为用了 UPSERT 就自然幂等。

批次幂等则用管道身份与 batchId 的组合判断同一批是否已经提交。batchId 不是所有作业之间全局唯一的编号,删除 Checkpoint 重建查询时也要重新处理身份范围,避免新任务的第一个批次被当成旧批次跳过。

完成标记必须与完整效果绑定

一种可行的数据库落地思路是:分布式写入带批次标识的暂存表,允许失败重试;完整落地并校验后,再由目标数据库在受控事务里合并数据、登记该批完成。暂存记录自身也要有稳定键,重复尝试不能制造无法识别的多份输入。

完成标记与目标合并应在同一个可提供原子性的边界内。先登记成功再写数据,会在中途失败时丢数据;先写数据再单独登记,失败重放又可能重复生效。只有这个原子性条件满足,批次账本才不只是另一张记录表。

外部已提交,为什么仍会重放这一批

一个典型场景:账本有了,汇总仍然翻倍

比如一个批次由多个分区并行写入。部分分区成功后,另一个分区连接超时,批次整体失败。开发发现没有成功标记,重启后重新执行全部写入,其中已成功分区又执行一次增量累加,金额就变大了。

最初以为 Spark 重复读取源数据;检查 Kafka 位点却不足以解释,因为读取重放本来就是恢复过程的一部分。真正缺少的是部分成功情况下的目标端幂等。只验证整批成功后的第二次运行,根本没有覆盖这个故障窗口。

修正可以采用稳定事件键去重、按版本覆盖结果,或使用前述暂存加原子合并方案。不要把所有数据 collect 到 Driver 来模拟一个“大事务”,那会把一致性问题转化成内存和吞吐瓶颈。

批次账本要和目标效果在一起提交

写两个系统,不会自动形成一个事务

同一批先写数据库 A 再写系统 B,A 成功而 B 失败时,重放必须允许安全地再次触达 A。每个目标都需要自己的幂等保证和进度记录;若业务要求跨目标同时可见,需要额外的发布协议、事务能力或可对账的补偿流程。

缓存批次 DataFrame 可以减少多次写出的重复计算,却不能保证多个目标原子提交。吞掉异常并返回成功更加危险:Spark 可能继续推进,而失败目标永远没有得到补写机会。

用故障窗口来验收,而不是只跑通一次

至少测试外部提交后进程退出、部分分区成功、重复回调、目标超时但实际已提交,以及新 Checkpoint 启动。核对事件唯一数、实体最终版本、批次账本和目标间差异,不仅检查“任务恢复成 RUNNING”。

如果回调依赖当前时间或不断变化的外部维表,同一批重放的结果也可能变化。还要约束输入和转换的可重复性,明确按事件当时版本还是处理时最新版本计算,才能判断哪一种结果算正确。

参考资料

刷题进度保存在当前浏览器