给模型降级链加冷却,避免反复撞故障
作者结合 n8n 生产工作流说明,仅有重试上限和退避仍会让每个任务重复访问已故障的供应商,拖慢队列。建议记录供应商故障状态并设置冷却,同时遵循 Retry-After、加入随机抖动。
完整中文译文
这是我公开记录如何在生产环境中运行 n8n LLM 工作流的系列文章之一。这里的内容都来自我实际销售并提供支持的模板——错误是我踩过的,修复方案也都经过测试。
在故障服务上白白耗掉五次尝试的调用链
我曾经很满意自己设计的一条 fallback 调用链:先调用主模型,再依次尝试两个更便宜的备用模型,总共五次尝试,重试次数有上限,还配了退避机制。纸面上看很完善。后来,有一家 provider 宕机了一下午,这条调用链就严格按照设计执行:先对已经宕机的 provider 发起尝试,再尝试备用模型,总共五次;接着把任务放进延迟队列,一小时后重新跑完整条调用链,又耗掉五次尝试。
队列没有停止工作,只是越来越慢,因为每个任务都要付出完整的代价,再次发现同一个事实:某家 provider 宕机了。fallback 调用链在正常工作,重试预算也在正常工作。缺少的是记忆:系统没有办法记住某家 provider 已经确认不可用,哪怕只记住十分钟。
没有冷却机制的 fallback 调用链,会在每个任务上重新认识同一次故障。
按顺序做好三件事
1. 遵循 Retry-After,再加入随机抖动
当 provider 对你限流时,通常会通过 Retry-After 响应头告诉你需要等待多久。照着做——这是 provider 在告诉你,等多久最划算;忽略它,可能会让限流变成封禁。然后,在退避时间中加入随机抖动。如果没有随机抖动,所有排队的任务都会在同一秒重试,刚刚恢复的 provider 就会同时遭到整个队列的冲击。随机抖动能把蜂拥而至的请求打散成细水长流。
2. 冷却:跳过刚刚连续失败的 provider
某家 provider 连续失败 N 次后——我用的是三次——就让它进入一段时间的冷却期,我默认设为十分钟,在此期间完全跳过它。不要为了“确认一下”再发探测请求。探测请求也是请求,发给已经宕机的 provider 就是在浪费。任务应转交给调用链中的下一家 provider;如果已经没有可用的 provider,就进入延迟队列。
冷却状态就存在我原本用于去重和重试的同一条任务记录里,字段包括 provider、consecutive_failures、cooldown_until。在调用 provider 之前,用一个 Code 节点读取这些状态:
// Code node: skip a provider in cooldown, don't probe it
const row = $json.provider_state; // from the job row
const now = Date.now();
if (row.cooldown_until && now < new Date(row.cooldown_until).getTime()) {
return [{ json: { ...$json, skip_provider: true, skip_reason: 'cooldown' } }];
}
return [{ json: { ...$json, skip_provider: false } }];
冷却期结束后,下一个任务会照常尝试调用这家 provider,不需要手动重置,也不需要单独的“健康检查”工作流。如果调用成功,consecutive_failures 就归零;如果再次失败,就重新进入冷却期。恢复过程能够自动完成,因为冷却状态是一个时间戳,而不是一个需要有人手动清除的标记。
3. 验证备用模型的输出,不能只看格式
备用模型即使返回了格式正确的 JSON,也可能给出一份毫无用处的草稿。因此,我会用同一组固定的样例输入测试调用链中的每个模型,按照同一套输出 schema 验证每个结果,然后继续检查草稿是否真正回应了需求,而不只是能否解析。我还会在任务记录里记下每份草稿由哪家 provider 生成,这样就能把备用模型生成的草稿与主模型生成的草稿分开审查。悄悄降低质量的 fallback,比直接报错的 fallback 更糟,因为那些看起来没问题的结果,往往没人会再检查。
冷却并不意味着放弃。provider 进入冷却期,不会导致任务被删除——任务要么继续交给调用链中的下一个 provider,要么留在延迟队列里等待,等冷却期结束后由扫描清理程序重新处理。“彻底失效”和“暂时不可用”的区别在于时间,而冷却机制让你争取到这段时间,不必在每个任务上反复付出代价。
有两个错误需要避免。第一,不要因为自己的 bug 触发冷却:4xx 参数校验错误说明你的请求有问题,并不代表 provider 宕机了。遇到 4xx 就冷却,只会把 bug 一次次藏起来,每次藏十分钟。应当针对 5xx、超时和限流触发冷却,而不是针对错误请求。第二,不要给所有 provider 设置同一套冷却参数。一周失败两次的 provider,和一小时失败两次的 provider,需要不同的冷却时长。我会在同一条记录中,为每家 provider 分别保存阈值和冷却时长,再根据已经记录的失败次数调整它们。
用这个测试证明冷却机制有效
模拟一家始终返回 500 的 provider,然后让十个任务跑过调用链,统计模拟服务实际收到的请求数。没有冷却机制时:五十次请求,每个任务十次,全都白费。有冷却机制时:第一个任务发起三次请求,接下来的九个任务都不会再发请求,直到冷却期结束。一定要在 provider 端计数,别在 n8n 端计数——无论有没有冷却机制,n8n 的执行次数看起来都会很正常,这正是这个 bug 难以被发现的原因。
fallback 调用链的作用,是让系统在 provider 故障时继续运行。一条刚发生失败就立刻忘掉的调用链,算不上扛住了故障,只不过是绕了更多步骤继续重试。给它一点记忆,哪怕只有十分钟。
我正在构建 AI Automation Lab,提供可直接用于生产环境的 n8n 工作流模板。免费的 AI Hook Generator 展示了我组织实际工作流的方式;我的开源模板放在 github.com/zhp910318/n8n-ai-automation-lab。
如需采取进一步行动,你可以考虑屏蔽此人和/或举报滥用行为。