先记住这个答案
重试责任应按故障边界和状态可见性划分,并为同一层操作指定一个主要控制者。接收方通常更适合恢复自己可判断且可安全重试的局部瞬时错误;发送方负责交接未确认、下游不可用或整项任务需要重新调度的情况。超时只表示结果未按时返回,不证明动作没有执行。恢复前应通过任务标识查询接单、产物和副作用状态,并在统一期限与重试预算内行动;业务拒绝、输入缺失和未知执行结果不能一律当作可直接重试的网络错误。
- 区分交接失败、执行失败与结果不可见
- 同一操作避免多层无协调重试放大流量
- 结果未知时先核对状态再决定是否重新执行
响应丢失不等于任务没有执行
发送方提交任务后超时,接收方可能尚未收到,也可能已经完成并在回传时断线。直接换一个任务标识重新提交,会让接收方无法识别两次请求属于同一逻辑工作。涉及发送、创建或扣减等动作时,后果尤其明显。
可采用稳定任务标识和可查询状态:未接单时重新投递同一任务;已运行时继续等待或请求取消;已完成时读取现有结果;状态未知时先核对下游记录。幂等键需要覆盖具体副作用,不能只在最外层日志中记录一个编号就宣称不会重复。
局部重试和重新调度承担不同责任
下游读取一个可重试的资源遇到瞬时错误,通常可以在本次任务期限内自行退避恢复,并把尝试次数记入任务记录。若缺少权限或输入版本已失效,它没有能力通过反复调用修复,应返回明确的阻塞原因让上游处理。
上游发现 worker 进程退出时,可以安排接管,但应先确认旧执行者是否仍可能写入。租约、代次标识和提交校验能够限制过期执行者继续提交结果。仅在控制台上把状态改成失败,不一定能停止仍在远端运行的外部动作。
用总预算约束各层恢复行为
如果调用链的每一层都自动重试数次,底层实际请求数会成倍增加。应检查 SDK、工具封装、worker 和调度器分别有哪些重试,让它们共享总期限或明确定义各自边界。退避和抖动能减轻同步冲击,但不能让错误的重试对象变得正确。
观测上分别记录逻辑任务数、执行尝试数和真实副作用次数。这样才能看出一次交接失败有没有变成多次外部写入。最终超过预算时保留可恢复状态与证据,向父任务说明已完成部分和未知部分,而不是把所有历史抹掉后从头再来。
容易答错的地方
- 谁先看到错误谁就立即重试整项任务
- 错误传播到多层时会触发重复恢复;应先约定局部执行与任务调度的责任,再按状态决定重试范围,并把 SDK 已经执行的重试计入总预算。
- 增加退避就能保证写操作安全
- 退避只调整请求时间,不能判断之前的写入是否成功;仍需要幂等处理、状态核对以及对未知结果的单独策略。
面试官还会怎么问?
参数校验失败可以交给 worker 自动重试吗?
只有它能根据明确规则修正参数且仍符合原始目标时才适合;原样重复错误输入没有收益,应返回字段错误或请求补充信息。
取消成功后能立刻启动新任务吗?
要确认取消语义是停止执行还是仅收到请求。已提交的外部动作可能无法撤销,新任务仍需检查旧任务的副作用状态和提交资格。
必须实现严格的恰好一次执行吗?
许多系统通过至少一次投递加幂等结果实现业务可接受行为。应说明保证覆盖的边界,不能把消息去重等同于所有外部副作用恰好发生一次。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。