LLM 工作流如何避免故障重试风暴
作者以 n8n 工作流在供应商宕机期间重试 200 次的经历,说明无节制重试如何消耗配额并阻塞后续任务。文章围绕等待间隔、尝试上限和停止条件,讨论退避、随机抖动与熔断策略。
AI 深度解读
一次供应商宕机,让作者的 n8n 工作流重试了 200 次,成功恢复次数却是 0。重复请求消耗配额、堆满日志,还因为执行一直没有结束,拖住了后面的任务。问题出在:系统只被告知失败后再试,却没有被告知什么时候该停。
作者给出的方案,是把重试拆成退避、随机抖动、熔断和错误分类,再用持续故障测试验证。对运行 LLM 工作流的开发者来说,这篇最有用的地方,是把「打开自动重试」推进成一套可以检查、可以验收的恢复策略。
重试需要回答三个问题,开关只能回答一个
n8n 的 Retry On Fail 很容易开启。对于瞬时故障,等 1 秒再试一次,确实可能成功。但供应商持续宕机时,同样的动作只会不断制造失败。
完整策略至少要决定:两次请求隔多久,最多尝试多少次,以及什么情况下停止调用。作者那次事故里,重试循环还占着执行,使排队任务一起等待。外部服务的问题因此传导到了自己的队列。
作者先按错误类型分流:429、5xx 和超时允许退避重试;4xx 参数校验错误、401/403 认证或权限错误不应重试。这里的 429 是明确列出的例外,不能被笼统的「4xx 不重试」覆盖。
重试的前提,是下一次请求存在成功的可能。
参数错误需要修正输入,凭证失效需要处理认证。重复发送相同请求,只会推迟真正的修复。作者的熔断器也只统计可重试错误,避免一批校验失败把正常供应商一起挡住。
退避控制频率,随机抖动打散并发
作者使用指数退避:等待时间按 1 秒、2 秒、4 秒、8 秒递增,最高到 5 分钟。
上限和递增同样重要。作者举例说,无上限的退避可能让一次 10 分钟故障,演变成每小时才尝试一次、晚一天才完成的任务。这是在说明风险,并没有给出对应实验数据。
另外两条规则更容易在实现中漏掉。
第一,供应商返回 Retry-After 时,优先采用这个响应头,而不是继续执行本地退避计划。不过,原文没有说清:当 Retry-After 超过 5 分钟时,上限如何处理,落地时需要明确约定。
第二,退避状态属于任务,不能只放在某次工作流执行里。一个已经等待 4 分钟的任务,被新执行接手后,不应又从 1 秒开始。这要求任务跨执行保留重试进度。
退避仍然解决不了同时重试的问题。假设 50 个任务同时失败,又使用相同的等待序列,它们就会同时再次请求。服务刚恢复,就可能迎来下一轮集中冲击,这就是惊群(thundering herd)。
作者采用全随机抖动(full jitter):对每一轮退避,在 0 到该轮等待上限之间随机取值。请求因此分散到一个时间窗口里。
原文认为,让单个任务多等一分钟,比让 50 个任务同秒重试更划算。这个取舍容易理解,但公式需要看准:按文中定义,随机值不会超过当轮上限。实现时应以明确的取值区间为准。
熔断器必须跨执行生效,还要允许一次探测
退避和抖动只是降低请求压力。供应商一直不可用时,还需要熔断器(circuit breaker)直接停止调用。
作者将供应商调用的连续失败阈值设为 5 次。达到阈值后,熔断器进入打开状态,在固定窗口内禁止继续请求。新任务直接进入延迟队列,并记录原因 provider circuit open。
窗口结束后进入半开状态,只放行 1 次探测。探测成功就关闭熔断,恢复正常流量;失败则重新打开,再等待一个窗口。原文没有提供窗口的具体时长。
要让这套机制跨执行成立,作者把熔断状态放在与任务队列相同的 Postgres 表中。工作流重启后,连续失败计数不会归零。延迟任务也保留原始载荷,恢复后按顺序执行。
打开熔断器还会触发告警,例如 provider X circuit open after 5 failures。作者希望立即知道供应商已经连续失败,而不是事后翻日志才发现。
验证方法也很具体:让模拟供应商连续 10 分钟每次调用都失败,然后恢复,检查四件事:
- 尝试次数受预算约束,目标是 5 次,而不是 200 次。
- 请求间隔逐渐拉长,并被随机打散。
- 第 5 次连续失败后,在熔断窗口结束前没有新调用,延迟任务带着原因留在队列中。
- 半开期间只有 1 次探测,恢复后排队任务各执行一次。
作者强调,要在模拟供应商侧统计调用次数。n8n 执行次数不足以证明外部请求究竟发出了多少次。
我的判断
这套方案最有价值的部分,是把请求频率、并发聚集和持续故障分别处理,并把状态从单次执行中移出来。对于有持久化队列的工作流,这比单独打开节点重试更完整。
但作者虽然声称修复经过测试,文章并未提供实现代码、测试记录或对照数据。5 次失败、5 分钟上限、full jitter,都是他们团队采用的配置,不能直接视为所有团队的最佳参数。
还有两个缺口。第一,「每个任务最多尝试 5 次」和「供应商连续失败 5 次后熔断」是两个不同约束,多任务并发时尤其如此,原文没有展开两者如何协调。第二,延迟任务恢复后按顺序执行,并不自动保证外部操作只发生一次。超时前请求可能已经完成,重试是否安全仍取决于业务是否支持幂等,原文没有说明。
因此,我更愿意把它作为故障恢复的设计清单,而不是一份可以直接复制的 n8n 配置。相比单节点自动重试,它更适合持续运行、任务不能轻易丢失的场景,同时也需要额外维护队列和共享状态。
能拿来做什么
- 检查现有 n8n LLM 工作流的错误出口,把 429、5xx、超时与参数、认证错误分别处理,避免所有失败都进入同一条重试路径。
- 为持久化任务保存重试进度,并明确
Retry-After、本地退避上限和尝试预算之间的优先级,防止换一次执行就重新计数。 - 如果用 Node 承接任务调度,让多个执行共享供应商熔断状态,并保证半开时只放行一次探测;只放在单个进程内的计数无法覆盖多个实例。
- 增加一次「持续失败 10 分钟后恢复」的模拟测试,在供应商入口统计实际请求,同时核对延迟原因、探测数量和恢复后的重复执行情况。