AI 任务失败了三次之后去哪了?聊聊「死信队列」

AI 任务失败了三次之后去哪了?聊聊「死信队列」

一、为什么普通重试机制不够用

任务跑在异步队列里,失败后按退避策略重试,这是第一道防线。但重试本身有边界,绕不过三个事实:

  • 重试次数或时限用完后,任务必须有一个明确的「最终去处」,不能就地消失;
  • 有些失败重试无意义——工具返回的是业务级拒绝(权限不足、参数非法),不是网络抖动,重试 100 次结果一样;
  • 失败任务不集中管理,排查时只能翻日志,没法批量看「现在有多少任务卡在失败状态、都是什么原因」。

死信队列(DLQ)补的就是这个缺口:把「重试耗尽」或「判定为不可重试」的任务,从主队列移出来,放进一个可观察、可人工介入、可选择性重放的独立通道。

二、什么该打进死信:先做失败分类

不是所有失败都该直接进 DLQ,先分清类型,能省下大量无意义的配额消耗:

失败类型该不该进 DLQ原因
网络超时、限流 429否,先走标准重试大概率是瞬时问题,重试能自愈
工具返回业务拒绝(权限不足、参数非法)是,并跳过剩余重试直接进重试不会改变结果,还浪费配额、拖长等待
模型输出连续多次不符合预期格式达到重试上限后进可能是提示词本身有问题,需要人看
依赖的外部服务持续不可用(超过熔断阈值)是避免整条队列被这一个依赖拖死
执行中途进程崩溃或被强杀视情况,恢复后先补一次重试要区分是任务本身的问题还是基础设施问题
核心判断标准只有一句:这个任务如果原样再跑一次,结果会不会不一样。会变——继续重试;不会变——直接死信,别浪费重试次数。

三、死信记录不能只写「失败了」

一条死信至少要包含足够排查和重放的信息,常见字段结构是这样的:任务 ID、原始输入、失败原因(发生在哪个阶段:模型调用/工具调用/结果校验/未知,加上错误码、错误信息、已重试次数)、执行轨迹、入队时间、当前状态(待复核/已重放/已丢弃)。

其中最容易忽略、但最关键的是执行轨迹里的「已完成步骤」。AI 任务往往是多步的,失败发生在第 3 步,不代表前 2 步没有产生副作用——比如一封邮件已经发出去了。不记这个字段,重放时要么重复执行已完成的步骤,要么根本不知道从哪一步接着跑。

四、进了死信之后,三条出路

  • 告警 + 人工介入:涉及资金损失、外部副作用(已扣款、已发送)的任务,必须告警,不能静默堆积。人看完后手动决定重放、丢弃还是走补偿流程。
  • 规则化自动重放:失败原因明确可修复时(比如当时依赖服务不可用、现在已恢复),配一条规则在条件满足后自动重新入队,不用每次都点一下。
  • 归档丢弃:确认已无业务价值(用户请求过期、数据源失效),标记为已丢弃并归档,避免队列本身无限膨胀。

重放前还有一件必须先做的事:检查执行轨迹,判断是从头重放还是从最后一个检查点续跑。无脑从头重放,对已经产生真实副作用(发消息、扣费、写外部系统)的步骤就是重复执行——这正是死信重放和普通消息重试最大的差异点。

五、和传统消息队列 DLQ 的三个差异

差异传统消息队列AI 任务
确定性同样输入基本得到同样处理模型输出本身有随机性,「原样重放」不等于「结果一致」,重放是给一次新机会,不是复现结果
副作用任务通常设计成幂等,重放安全多步任务中途调用外部工具,副作用可能不可逆,重放前必须先查轨迹
失败分类看 HTTP 状态码就能判断「输出格式不对」「参数被模型编错」这类要额外分类逻辑,光看状态码不够

六、三个必踩的误区

  • 把死信队列当日志用:只写不读,堆积无人处理,等同于换了个地方消失。
  • 所有失败无差别进死信:瞬时错误也塞进去,队列被噪音塞满,真正需要人看的问题反而被淹没。
  • 重放不检查副作用:最容易踩的一个。任务涉及资金、外部通知时,重复执行的代价往往远大于任务失败本身。

一句话收束:死信队列的价值不在「存」,而在「有人看得见、有人管得动、重放不出事」。