订阅更新

数据任务重试怎么设计?为什么越重试故障越严重?

面试问 260 约 4 分钟读完 1 次阅读

本文目录
  1. 面试怎么回答
  2. 先分三种结果,别遇到异常就重跑
  3. 重试之前,先确定重复执行会发生什么
  4. 多层重试,为什么会出现乘法?
  5. 退避、抖动、次数和总时限要一起设计
  6. 一个典型场景:下游恢复了,积压还在恶化
  7. 可靠的失败,也比无限等待更有价值
  8. 参考资料

面试怎么回答

我会先分错误:瞬态故障才适合自动重试,语法或权限问题应直接告警,提交超时要当作结果未知处理。其次明确幂等键和提交边界,避免重复写。重试要有指数退避和随机抖动,同时限制总次数、总时限和并发。还要梳理调度器、脚本与 SDK 的多层重试,选清负责层;恢复后分批补偿,并监控重试流量是否挤占正常任务。

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

详细讲解

数据库只是短暂变慢,随后调度器开始重跑,脚本开始循环,SDK 也在重试。原本还有机会恢复的下游,被越来越多的请求压住了。重试是可靠性手段,但设计不好,它也会成为故障放大器。

重试需要错误分类、幂等边界和总预算;没有退避与并发约束,重试会把局部故障放大。

先分三种结果,别遇到异常就重跑

第一种是可能自行恢复的瞬态错误,例如临时网络故障、短时限流。满足安全条件后,可以在预算内重试,但限流尤其需要等待,不能立即提高请求频率。

第二种是当前输入和配置不变就不会恢复的错误,例如 SQL 语法错误、明确缺少权限或确定的字段类型不匹配。反复执行相同请求,通常只会重复失败,应该尽快暴露给负责人员。

第三种是结果未知,例如服务端可能已经提交,但客户端没有收到确认。它不等于确定失败;应该先按业务标识或提交记录核对,或依靠明确的幂等机制安全重试。

错误分类要结合接口语义和实际错误码,不是看到 TimeoutException 就统一归到“没执行成功”。

重试之前,先确定重复执行会发生什么

同一批数据再次写入,会覆盖同一业务键,还是追加一份?任务先写完数据库、再发送通知,如果只在最后记录任务成功,通知失败后的重跑会不会把数据库再写一次?

应明确每个阶段的提交边界,用稳定的业务键、批次号和输入版本标识一次逻辑操作。重试生成新批次号,会把原来的重试变成一项新工作,反而绕开已有去重。

读任务重试也并非完全没有影响:会重复消耗资源,而且底层数据可能已经变化。要求结果可复现时,还要固定输入分区、快照或截止时间,不能只保证代码相同。

多层重试,为什么会出现乘法?

假设调度器最多尝试三次,每次脚本内部最多尝试三次,SDK 对每次调用也最多尝试三次。这里三次都包含首次执行,最坏情况下,下游可能收到 3 × 3 × 3,也就是 27 次请求。

这只是一个明确假设下的例子,不是哪个组件的默认值。如果大家说的“重试三次”不包含首次,计算又会不同。所以设计文档必须区分最大尝试次数和额外重试次数。

多层重试的请求放大过程

通常应选择一个了解上下文的层来负责主要重试,其他层保留必要但受限的机制,并把总预算算清。不能只因为每一层单独看起来合理,就认为组合后一定合理。

退避、抖动、次数和总时限要一起设计

指数退避让后续重试逐渐拉开间隔,随机抖动则避免大量任务在同一秒再次冲向下游。一个示意方式是:第 n 次重试等待时间,在 0 到 min(最大间隔, 基础间隔 × 2 的 n 次方) 之间随机选择。

具体参数取决于服务延迟、业务 SLA 和限流提示,不能照搬固定数字。还应设置最大尝试次数、总截止时间和并发上限。只有退避没有终止条件,系统可能在故障消失后仍背着一大串过期任务。

总预算应包含前面的执行耗时,不能每层重新获得一整段超时时间。下一次等待加执行预算已经超过业务截止时间时,就应停止自动尝试,进入可观测的失败或补偿流程。

一个典型场景:下游恢复了,积压还在恶化

比如数据库短时连接紧张,任务快速失败后立即重试,多个调度批次又同时到点。最初以为重试还不够积极,继续缩短间隔,结果连接竞争更严重,正常任务也开始排队。

根因是三层重试与批量调度叠加,恢复流量挤占了正常流量。原监控只展示每个任务重试了几次,没有按下游统计总请求数和并发,因此看不到乘法放大。

改进应收敛重试层,限制并发、增加退避抖动,并把重试流量单独计量。恢复后按优先级分批补跑,先验证一小批的成功率与资源占用,再逐步放量,而不是把所有失败任务一起点重跑。

可靠的失败,也比无限等待更有价值

超过预算后,要保留失败原因、输入范围、幂等标识和已完成阶段,让后续人员能安全继续。需要人工处理的任务应进入明确清单,而不是在后台无限循环,既不成功也不告警。

衡量重试设计,不只是看它救回多少任务,还要看是否制造重复数据、是否冲击正常链路,以及最终失败能不能被解释和恢复。

参考资料

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