任务队列反复失败,先分清重试、阻塞和结果待核对

同一条任务失败了很多次,下一步仍然是重试。

如果授权已经撤销、请求参数一直不合法,延长队列只能重复相同的失败。另一个容易被忽略的情况是请求超时:客户端没有拿到响应,远端却可能已经完成写入。再次发送可能产生第二笔记录。

设计重试策略前,需要保留失败的原因,并判断重复执行会带来什么后果。

不要把不同结果都压成 null

外部调用只返回 true/false 或 value/null,会让下游丢失判断依据。参数不合法、凭据不可用、网络暂时中断都变成同一个失败值,消费者只能套用统一的重试次数。

可以在调用适配层把结果转换成下面几种语义。这是一种应用层设计,具体错误码仍应按服务提供方的文档映射。

结果 示例 后续处理
正常跳过 输入不满足业务执行条件 保存跳过原因,不计为调用失败
持续性阻塞 参数需要修正,或授权需要重新取得 停止自动重试,等待输入或配置变化
瞬时失败且重复执行可控 短暂不可用、部分限流场景 在次数与时间预算内安排重试
结果待核对 写请求超时,无法确认远端是否完成 先查远端状态,再决定是否重发

这里不能只按 HTTP 状态码分组。例如,认证失败可能需要先刷新凭据;刷新后仍然缺少权限,就应进入待修复状态。限流是否能稍后恢复、应等待多久,也取决于接口契约和返回信息。

请求超时不等于远端没有执行

假设一次"创建记录"请求已经到达远端,但返回响应的连接中断了。客户端只看到超时,无法据此断定创建失败。

RFC 9110 的幂等性说明指出,非幂等请求不能仅因为通信失败就自动重试,需要能够确认该请求实际上可幂等执行,或确认原请求没有应用。

对存在副作用的接口,可以考虑两条路径:

  • 服务端支持幂等机制时,按接口要求复用同一业务操作的幂等键,并核对有效期、作用范围和重复请求语义。
  • 无法确认重试安全时,使用业务标识查询远端结果;查不到也要考虑接口的可见性延迟,不能立刻当成未执行。

在客户端随手增加一个 requestId,不会让服务端自动具备幂等能力。关键是对方是否识别这个标识,以及重复请求会如何处理。

把重试资格写成一个小函数

下面的 JavaScript 示例只负责选择下一步,不实现队列、网络请求和等待逻辑。输入已经由适配层分类,failedAttempts 表示这次失败后累计执行过多少次;示例最多允许三次执行。

javascript 复制代码
const MAX_ATTEMPTS = 3;

function nextAction(result) {
  if (result.kind === "not_applicable") return "skip";
  if (result.kind === "permanent") return "block";
  if (result.kind !== "transient") return "review";

  if (!Number.isInteger(result.failedAttempts) ||
      result.failedAttempts < 1 ||
      typeof result.mayHaveCommitted !== "boolean" ||
      typeof result.retrySafe !== "boolean") {
    return "review";
  }

  if (result.mayHaveCommitted && !result.retrySafe) {
    return "reconcile";
  }

  return result.failedAttempts < MAX_ATTEMPTS
    ? "retry"
    : "block";
}

retrySafe 需要有明确依据,例如这是没有写入副作用的读取操作,或服务端已经按契约提供幂等保障。不能让模型根据一段报错文本随意把它设成 true。

一个已经失败两次、远端可能完成写入、又没有幂等保障的任务,会得到 reconcile。即使次数还有余额,也不会直接重发。

达到次数上限之后仍可能需要核对远端结果。因此代码先判断"结果是否不确定",再判断能否继续尝试。block 与 reconcile 都不会直接触发新请求,但需要不同的后续处理。

获得重试资格后再计算等待时间

重试资格与调度时间是两件事。确定可以重试后,还需要限制请求总量。

AWS 的超时、重试与抖动退避说明讨论了重试放大后端压力、分层重试叠加,以及通过退避和随机抖动分散请求的问题。这里可以采用以下设计:

  • 只在一个明确的调用层负责重试,避免 SDK、服务和队列各自再试一轮。
  • 同时限制执行次数、单次超时和任务总截止时间。
  • 使用带上限的退避,并通过随机抖动分散同时失败的请求。

收到 Retry-After 时,按服务端给出的等待建议安排后续请求。它可能是秒数,也可能是 HTTP 日期,不能把它固定当成数字解析。MDN 的 Retry-After 说明

如果等待时间已经超出任务总预算,就结束或延后该任务,而不是为了赶截止时间缩短服务端要求的间隔。上面的决策函数并没有实现这些调度能力,接入真实队列时需要另外补齐。

验证时检查容易被误判的分支

本文示例进行了十组本地断言,覆盖正常跳过、持续性阻塞、未知错误、安全重试、结果待核对、达到次数上限和不完整输入,均通过。

有几项检查尤其值得保留:

输入条件 预期动作
瞬时失败,确认没有写入,第一次执行失败 retry
可能已经写入,且没有重试安全保障 reconcile
可能已经写入,但具备明确的幂等保障,次数有余额 retry
明确需要修复配置 block
符合正常跳过条件 skip
错误类型未知或关键字段缺失 review

这些断言验证的是决策函数,不代表第三方接口、队列恢复或真实业务写入已经验收。上线前还应模拟响应丢失、幂等键过期、重试预算耗尽,以及多个任务同时被限流的情形。

排查反复失败的任务时,可以先检查错误信息是否被压扁,再检查重复执行是否安全,最后才调整重试次数和等待间隔。这样每次重试都有依据,无法自愈的问题也能及时转入可处理的状态。

相关推荐
熊猫钓鱼>_>1 小时前
四种书签,一条时间线:一个读书 App 的记忆颗粒与它的边界案例【鸿蒙心迹】
开发语言·华为·ai编程·harmonyos·鸿蒙·openharmony·鸿蒙pc
努力努力再努力wz2 小时前
【Lua 入门系列】Lua 函数深入理解:函数对象、多返回值、可变参数、闭包、递归与尾调用优化
开发语言·lua
jktantan2 小时前
每个节点 41 字节:把一棵百万节点树拆进 14 个 typed array
前端·javascript·vue.js·算法·typescript
飞浪纪元[FWC–FE]2 小时前
从零到一键绿:Qt 5.14.2 + VS2019 开发环境搭建、Kit 套件配置与构建错误修复完全手册
开发语言·qt
Wang's Blog2 小时前
Java 项目实战:交友项目 - 联系人列表
java·开发语言·交友
我命由我123452 小时前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理
架构师伯子明2 小时前
FFmpeg C++学习(一):编译和demo
开发语言·c++·学习
海域云SeaArea_3 小时前
Windows Server 2008 R2 从零安装 IIS7.5(二):php.ini 配置
开发语言·windows·php
叼烟扛炮3 小时前
[LangChain] 学习笔记(02):LCEL 链式调用与动态模型配置实战
服务器·开发语言·网络·笔记·学习·langchain