26d - 错误处理模式
本文是《AI Agent 实战手册》第 26 章第 4 节。 上一节:26c-工作流蓝图集 | 下一节:26e-Webhook与高级集成
概述
生产环境中的 AI 工作流不可避免地会遇到 API 超时、速率限制、模型幻觉、数据格式异常等各类故障。本节系统讲解 n8n 和 Make.com 的错误处理机制,覆盖死信队列(Dead Letter Queue)、指数退避(Exponential Backoff)、降级路径(Fallback/Degradation Path)和告警(Alerting)四大核心模式,帮助你构建能在故障中自愈、在异常中降级、在失败后告警的生产级工作流。
1. 错误处理基础:故障分类与策略选择
1.1 AI 工作流常见故障类型
| 故障类型 | 典型表现 | 频率 | 严重度 | 推荐策略 |
|---|---|---|---|---|
| 瞬时网络错误 | 连接超时、DNS 解析失败 | 高 | 低 | 自动重试 + 指数退避 |
| API 速率限制 | HTTP 429 Too Many Requests | 中高 | 中 | 指数退避 + 队列限流 |
| 模型服务不可用 | HTTP 500/502/503 | 中 | 高 | 模型降级 + 备用提供商 |
| 认证过期 | HTTP 401/403 | 低 | 高 | 告警 + 人工介入 |
| 数据格式异常 | JSON 解析失败、字段缺失 | 中 | 中 | 数据验证 + 降级路径 |
| LLM 输出异常 | 幻觉、格式不符、拒绝回答 | 中 | 中高 | 输出验证 + 重试/降级 |
| 上游服务变更 | API 字段变更、Schema 漂移 | 低 | 高 | 告警 + 死信队列 |
| 资源耗尽 | 内存溢出、磁盘满 | 低 | 极高 | 告警 + 熔断 |
1.2 错误处理决策树
工作流执行出错
├── 错误是否为瞬时故障?(网络超时、429、5xx)
│ ├── 是 → 自动重试(指数退避)
│ │ ├── 重试成功 → 继续执行
│ │ └── 重试耗尽 → 进入死信队列 + 告警
│ └── 否 → 错误是否可降级?
│ ├── 是 → 执行降级路径(备用模型/缓存/默认值)
│ │ └── 降级成功 → 继续执行(标记降级)
│ └── 否 → 错误是否为数据问题?
│ ├── 是 → 记录到死信队列 + 跳过当前项
│ └── 否 → 终止执行 + 紧急告警1.3 错误处理工具与服务
| 工具/服务 | 用途 | 价格 | 适用场景 |
|---|---|---|---|
| n8n Error Trigger | 全局错误捕获与告警 | 免费(内置节点) | 所有 n8n 工作流 |
| n8n Error Workflow | 指定工作流的错误处理链 | 免费(内置功能) | 生产工作流错误路由 |
| n8n Retry on Fail | 节点级自动重试 | 免费(内置功能) | 瞬时故障自动恢复 |
| Make.com Error Handler | 模块级错误处理路由 | 免费(内置功能) | Make 场景错误分支 |
| Make.com Break 指令 | 不完整执行队列 + 自动重试 | 免费(内置功能) | 类死信队列模式 |
| Make.com Incomplete Executions | 失败执行存储与重放 | 免费(内置功能) | 手动/自动重试失败任务 |
| Slack API | 告警通知渠道 | 免费(基础)/ Pro $8.75/人/月 | 团队实时告警 |
| PagerDuty | 事件管理与升级 | $21/用户/月起 | 关键业务告警升级 |
| Sentry | 错误追踪与聚合 | 免费层可用 / Team $26/月 | 错误趋势分析 |
| Grafana + Prometheus | 监控仪表板 | 免费(自托管)/ Cloud 免费层可用 | 工作流健康度监控 |
| Supabase / PostgreSQL | 错误日志持久化 | 免费层可用 / Pro $25/月 | 死信队列数据存储 |
| Google Sheets | 轻量错误日志 | 免费 | 小规模错误追踪 |
| Redis | 重试状态与计数器 | 免费(自托管)/ Cloud $5/月起 | 分布式重试状态管理 |
| BetterStack (Logtail) | 日志聚合与告警 | 免费层 1GB/月 / Pro $25/月 | 集中式日志管理 |
💡 价格信息截止日期:2025 年 7 月。请以各服务官方最新定价为准。
2. n8n 错误处理机制详解
2.1 n8n 错误处理三层架构
n8n 提供三个层级的错误处理机制,从细粒度到全局依次为:
┌─────────────────────────────────────────────┐
│ 第三层:Error Workflow(全局错误工作流) │
│ ┌─────────────────────────────────────────┐ │
│ │ 第二层:Error Output(节点错误输出分支) │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ 第一层:Retry on Fail(节点自动重试)│ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘| 层级 | 机制 | 作用范围 | 触发条件 | 典型用途 |
|---|---|---|---|---|
| 第一层 | Retry on Fail | 单个节点 | 节点执行失败 | 瞬时故障自动恢复 |
| 第二层 | Error Output | 单个节点的下游分支 | 节点执行失败且重试耗尽 | 降级路径、错误数据路由 |
| 第三层 | Error Workflow | 整个工作流 | 工作流执行失败(未被捕获) | 全局告警、日志记录 |
2.2 第一层:Retry on Fail(节点级自动重试)
配置方法
在任意节点的 Settings 面板中启用:
- 打开节点设置 → Settings 标签
- 开启 Retry on Fail
- 配置参数:
- Max Tries:最大重试次数(建议 2-5 次)
- Wait Between Tries (ms):重试间隔(毫秒)
操作步骤
步骤 1:识别需要重试的节点
优先为以下类型的节点启用重试:
- HTTP Request 节点(外部 API 调用)
- 数据库操作节点(Postgres、MySQL、MongoDB)
- 第三方服务节点(Slack、Gmail、Notion)
- AI/LLM 节点(OpenAI、Anthropic、Google AI)
步骤 2:配置重试参数
{
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 2000
}推荐配置表:
| 节点类型 | Max Tries | Wait Between (ms) | 说明 |
|---|---|---|---|
| HTTP Request(一般 API) | 3 | 2000 | 覆盖大部分瞬时故障 |
| OpenAI / Anthropic 节点 | 3 | 5000 | LLM 服务恢复较慢 |
| 数据库写入 | 2 | 1000 | 避免重复写入 |
| 邮件发送 | 2 | 3000 | 防止重复发送 |
| Webhook 回调 | 3 | 2000 | 确保通知送达 |
步骤 3:注意幂等性
⚠️ 关键提醒:启用重试前,必须确认操作是幂等的(重复执行不会产生副作用)。对于非幂等操作(如创建订单、发送邮件),需要在业务逻辑中加入去重机制。
// n8n Code 节点:幂等性检查示例
const itemId = $input.item.json.id;
const processedKey = `processed_${itemId}`;
// 检查是否已处理(使用 n8n 静态数据或外部存储)
const staticData = $getWorkflowStaticData('global');
if (staticData[processedKey]) {
return []; // 已处理,跳过
}
// 标记为已处理
staticData[processedKey] = new Date().toISOString();
return $input.item;2.3 第二层:Error Output(节点错误输出分支)
Error Output 是 n8n 中实现降级路径的核心机制。当节点失败时(且重试耗尽),错误数据会被路由到专门的错误输出分支,而不是终止整个工作流。
配置方法
- 选中目标节点
- 在节点设置中找到 On Error 选项
- 将其从默认的
Stop Workflow改为Output Error Data - 节点底部会出现红色的错误输出端口
- 从错误输出端口连接后续的降级处理节点
错误输出数据结构
当节点失败时,错误输出会包含以下数据:
{
"error": {
"message": "Request failed with status code 429",
"name": "NodeApiError",
"description": "Rate limit exceeded",
"httpCode": "429"
},
"pairedItem": {
"item": 0
}
}典型应用模式
[HTTP Request: 主 API]
├── ✅ 成功输出 → [后续处理节点]
└── ❌ 错误输出 → [Switch: 错误分类]
├── 429 → [Wait 节点] → [HTTP Request: 重试]
├── 401 → [Slack: 认证过期告警]
├── 500 → [HTTP Request: 备用 API]
└── 其他 → [Google Sheets: 记录错误] → [Slack: 通用告警]操作步骤:构建错误分类路由
步骤 1:设置错误输出
将目标节点的 On Error 设置为 Output Error Data。
步骤 2:添加 Switch 节点进行错误分类
// Switch 节点条件配置
// 条件 1:速率限制
{{ $json.error.httpCode === "429" }}
// 条件 2:认证失败
{{ $json.error.httpCode === "401" || $json.error.httpCode === "403" }}
// 条件 3:服务端错误
{{ parseInt($json.error.httpCode) >= 500 }}
// 条件 4:其他错误(Fallthrough)步骤 3:为每种错误类型配置处理逻辑
- 429 速率限制:添加 Wait 节点(动态等待时间),然后重试
- 401/403 认证失败:发送紧急告警,终止执行
- 5xx 服务端错误:切换到备用服务或缓存数据
- 其他错误:记录日志,发送通用告警
2.4 第三层:Error Workflow(全局错误工作流)
Error Workflow 是 n8n 的全局安全网。当工作流中任何未被捕获的错误发生时,指定的 Error Workflow 会被触发。
配置方法
- 创建一个新的工作流,以 Error Trigger 节点开始
- 在需要监控的工作流中,打开 Workflow Settings
- 在 Error Workflow 下拉菜单中选择刚创建的错误工作流
Error Trigger 节点输出数据
{
"execution": {
"id": "231",
"url": "https://your-n8n.com/execution/231",
"retryOf": null,
"error": {
"message": "Request failed with status code 500",
"name": "NodeApiError"
},
"lastNodeExecuted": "HTTP Request",
"mode": "trigger"
},
"workflow": {
"id": "15",
"name": "AI 内容管线"
}
}操作步骤:构建集中式错误告警工作流
步骤 1:创建 Error Workflow
[Error Trigger]
→ [Code: 错误分级]
→ [Switch: 严重度路由]
├── 🔴 Critical → [PagerDuty: 创建事件] + [Slack: #alerts-critical]
├── 🟡 Warning → [Slack: #alerts-warning]
└── 🟢 Info → [Google Sheets: 错误日志]步骤 2:实现错误分级逻辑
// Code 节点:错误严重度分级
const error = $input.item.json.execution.error;
const workflow = $input.item.json.workflow;
const errorMessage = error.message || '';
let severity = 'info';
let emoji = '🟢';
// Critical:认证失败、数据库连接失败、支付相关
if (
errorMessage.includes('401') ||
errorMessage.includes('403') ||
errorMessage.includes('ECONNREFUSED') ||
workflow.name.toLowerCase().includes('payment') ||
workflow.name.toLowerCase().includes('billing')
) {
severity = 'critical';
emoji = '🔴';
}
// Warning:速率限制、超时、5xx
else if (
errorMessage.includes('429') ||
errorMessage.includes('timeout') ||
errorMessage.includes('ETIMEDOUT') ||
errorMessage.includes('500') ||
errorMessage.includes('502') ||
errorMessage.includes('503')
) {
severity = 'warning';
emoji = '🟡';
}
return {
severity,
emoji,
workflow_name: workflow.name,
workflow_id: workflow.id,
error_message: errorMessage,
execution_id: $input.item.json.execution.id,
execution_url: $input.item.json.execution.url,
timestamp: new Date().toISOString()
};步骤 3:配置 Slack 告警消息
提示词模板:Slack 告警消息格式
${emoji} *工作流错误告警*
*工作流*:${workflow_name}(ID: ${workflow_id})
*严重度*:${severity}
*错误信息*:${error_message}
*执行 ID*:${execution_id}
*时间*:${timestamp}
<${execution_url}|查看执行详情>步骤 4:将 Error Workflow 绑定到所有生产工作流
💡 最佳实践:创建一个通用的 Error Workflow,然后在所有生产工作流的设置中统一指向它。这样可以集中管理告警逻辑,避免每个工作流重复配置。
3. Make.com 错误处理机制详解
3.1 Make.com 五大错误处理指令
Make.com 提供五种错误处理指令(Error Handler Directives),每种对应不同的故障恢复策略:
| 指令 | 行为 | 执行状态 | 后续模块 | 适用场景 |
|---|---|---|---|---|
| Rollback | 立即停止执行,回滚所有操作 | ❌ 错误 | 不执行 | 默认行为,数据一致性要求高 |
| Commit | 立即停止执行,提交已完成操作 | ✅ 成功 | 不执行 | 部分成功可接受的场景 |
| Ignore | 忽略错误,跳过后续模块 | ✅ 成功 | 不执行 | 非关键操作,允许跳过 |
| Resume | 提供替代输出,继续执行 | ✅ 成功 | 继续执行 | 降级路径,使用默认值 |
| Break | 存入不完整执行队列,可自动重试 | ⚠️ 警告 | 不执行 | 类死信队列,延迟重试 |
3.2 错误处理指令详解
Rollback(回滚)
行为:停止场景执行,回滚所有已执行的操作(如果服务支持事务回滚)。
配置方法:
- 右键点击可能出错的模块
- 选择 Add error handler
- 在错误路由中添加 Rollback 指令模块
适用场景:
- 金融交易:转账失败时必须回滚
- 数据同步:部分同步会导致数据不一致
- 批量操作:一个失败则全部回滚
[模块 A: 扣款] → [模块 B: 发货] → [模块 C: 通知]
│
└── ❌ Error Handler → [Rollback]
(模块 A 的扣款也会被回滚)Commit(提交)
行为:停止场景执行,但保留已完成操作的结果。
适用场景:
- 批量处理中,部分成功是可接受的
- 日志记录类操作,已写入的日志无需回滚
Ignore(忽略)
行为:忽略当前模块的错误,跳过该模块后续的所有模块,场景标记为成功。
适用场景:
- 非关键的通知发送(如 Slack 消息发送失败不影响核心流程)
- 可选的数据增强步骤
[获取数据] → [AI 摘要生成] → [发送 Slack 通知]
│
└── ❌ Error Handler → [Ignore]
(通知失败不影响整体流程)Resume(恢复)
行为:提供一个替代输出值,场景使用该替代值继续执行后续模块。这是实现降级路径的核心指令。
配置方法:
- 添加 Error Handler
- 在错误路由中添加 Resume 指令模块
- 在 Resume 模块中配置替代输出(硬编码的默认值或从其他来源获取的备用数据)
适用场景:
- AI 摘要生成失败时,使用原文前 200 字作为替代
- 图片生成失败时,使用默认占位图
- 翻译失败时,保留原文
[AI 摘要生成]
├── ✅ 成功 → [后续处理](使用 AI 摘要)
└── ❌ Error Handler
→ [Text: 截取原文前200字]
→ [Resume](使用截取文本作为替代输出)
→ [后续处理](使用替代摘要继续)Break(中断 + 不完整执行队列)
行为:将当前执行存入”不完整执行”(Incomplete Executions)队列,可配置自动重试。这是 Make.com 中最接近死信队列的机制。
配置前提:
- 进入场景设置
- 开启 Allow storing of incomplete executions → 设为
Yes
配置参数:
- Automatically complete execution:是否自动重试
Yes:自动重试(类似自动重放死信队列)No:手动处理(需要人工在 Incomplete Executions 面板中重放)
- Number of consecutive attempts:自动重试次数(1-10)
- Interval between attempts:重试间隔(分钟)
适用场景:
- 第三方 API 临时不可用,需要稍后重试
- 批量处理中某些项目失败,需要单独重试
- 需要人工审查后决定是否重试
[HTTP Request: 调用外部 API]
│
└── ❌ Error Handler
→ [Break]
├── 自动重试:3 次,间隔 15 分钟
└── 重试耗尽 → 留在 Incomplete Executions 队列
→ 人工在面板中查看并处理3.3 Make.com 错误处理指令选择决策表
| 场景 | 推荐指令 | 原因 |
|---|---|---|
| 支付/转账失败 | Rollback | 必须保证数据一致性 |
| 批量邮件发送,个别失败 | Ignore | 个别失败不影响整体 |
| AI 生成失败,有备用方案 | Resume | 使用默认值继续流程 |
| 外部 API 临时不可用 | Break | 存入队列稍后重试 |
| 日志写入失败 | Commit | 已完成的操作无需回滚 |
| 关键数据同步失败 | Break + 告警 | 重试 + 通知人工介入 |
3.4 Make.com 错误处理操作步骤
步骤 1:为关键模块添加 Error Handler
- 右键点击目标模块
- 选择 Add error handler
- 系统会在该模块下方创建一条错误路由(虚线连接)
步骤 2:在错误路由中添加处理逻辑
错误路由中可以添加任意模块(如发送通知、记录日志),最后以一个指令模块结束:
[目标模块]
│
└── ❌ Error Route
→ [Slack: 发送错误通知]
→ [Google Sheets: 记录错误日志]
→ [Resume / Break / Ignore / Rollback / Commit]步骤 3:配置 Break 指令的自动重试
- 进入场景 Settings(齿轮图标)
- Allow storing of incomplete executions →
Yes - Automatically complete execution →
Yes - Number of consecutive attempts →
3 - Interval between attempts →
15(分钟)
⚠️ 注意:Break 指令的重试间隔是固定的(不支持指数退避)。如果需要指数退避效果,需要通过自定义逻辑实现(见第 4 节)。
4. 指数退避(Exponential Backoff)
4.1 指数退避原理
指数退避是一种渐进式重试策略:每次重试的等待时间按指数增长,避免在服务恢复期间产生”重试风暴”。
公式:
等待时间 = min(base_delay × 2^(attempt - 1) + random_jitter, max_delay)示例(base_delay = 1s, max_delay = 60s):
| 重试次数 | 等待时间(无抖动) | 等待时间(含抖动) |
|---|---|---|
| 第 1 次 | 1 秒 | 1.0 - 1.5 秒 |
| 第 2 次 | 2 秒 | 2.0 - 3.0 秒 |
| 第 3 次 | 4 秒 | 4.0 - 6.0 秒 |
| 第 4 次 | 8 秒 | 8.0 - 12.0 秒 |
| 第 5 次 | 16 秒 | 16.0 - 24.0 秒 |
| 第 6 次 | 32 秒 | 32.0 - 48.0 秒 |
| 第 7 次 | 60 秒(上限) | 60.0 秒(上限) |
💡 为什么需要随机抖动(Jitter)? 当多个工作流同时遇到同一个 API 的速率限制时,如果它们都在完全相同的时间点重试,会再次同时触发限制。添加随机抖动可以分散重试时间,降低碰撞概率。
4.2 n8n 中实现指数退避
n8n 内置的 Retry on Fail 使用固定间隔,不支持指数退避。需要通过 Code 节点 + Wait 节点手动实现。
方案一:Loop + Wait 模式(推荐)
[触发器] → [设置重试参数] → [Loop]
→ [HTTP Request: API 调用]
├── ✅ 成功 → [跳出循环] → [后续处理]
└── ❌ 错误输出 → [Code: 计算退避时间]
→ [IF: 是否超过最大重试次数?]
├── 是 → [跳出循环] → [错误处理/死信队列]
└── 否 → [Wait: 动态等待] → [回到循环开始]操作步骤
步骤 1:初始化重试参数
使用 Set 节点设置初始参数:
{
"attempt": 0,
"maxAttempts": 5,
"baseDelay": 1000,
"maxDelay": 60000
}步骤 2:实现退避时间计算
// Code 节点:计算指数退避等待时间
const attempt = $input.item.json.attempt + 1;
const baseDelay = $input.item.json.baseDelay || 1000;
const maxDelay = $input.item.json.maxDelay || 60000;
// 指数退避 + 随机抖动
const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
const jitter = Math.random() * exponentialDelay * 0.5;
const waitTime = Math.min(exponentialDelay + jitter, maxDelay);
return {
attempt,
maxAttempts: $input.item.json.maxAttempts,
baseDelay,
maxDelay,
waitTime: Math.round(waitTime),
waitTimeSeconds: Math.round(waitTime / 1000),
error: $input.item.json.error
};步骤 3:配置 Wait 节点
- 添加 Wait 节点
- Resume 设置为
After Time Interval - Amount 使用表达式:
{{ $json.waitTimeSeconds }} - Unit 设置为
Seconds
步骤 4:配置循环退出条件
使用 IF 节点判断:
条件:{{ $json.attempt >= $json.maxAttempts }}
├── true → 退出循环,进入错误处理
└── false → 继续循环,回到 API 调用方案二:Sub-Workflow 递归模式
对于复杂场景,可以将重试逻辑封装为独立的子工作流:
[主工作流]
→ [Execute Sub-Workflow: 带退避的 API 调用]
├── 输入:url, method, body, attempt, maxAttempts
├── 内部逻辑:
│ [HTTP Request]
│ ├── ✅ 成功 → 返回结果
│ └── ❌ 失败 → [计算退避] → [Wait] → [递归调用自身]
└── 输出:API 响应或最终错误4.3 Make.com 中实现指数退避
Make.com 的 Break 指令只支持固定间隔重试。要实现指数退避,需要使用以下模式:
方案:Clone + Sleep + Resume 模式
[模块 A: API 调用]
│
└── ❌ Error Handler
→ [Set Variable: retry_count + 1]
→ [Router]
├── 条件:retry_count <= max_retries
│ → [Sleep: 动态计算时间]
│ → [模块 A 的克隆: 重试 API 调用]
│ ├── ✅ 成功 → [Resume](使用重试结果继续)
│ └── ❌ 失败 → [回到 Router]
└── 条件:retry_count > max_retries
→ [Slack: 告警通知]
→ [Google Sheets: 记录到死信队列]
→ [Ignore / Break]操作步骤
步骤 1:设置重试计数器
在场景开始处添加 Set Variable 模块:
- 变量名:
retry_count - 初始值:
0
步骤 2:计算动态等待时间
在错误路由中添加 Set Variable 模块:
变量名:wait_seconds
值:{{min(pow(2, retry_count) * 1 + floor(random * pow(2, retry_count) * 0.5), 60)}}步骤 3:添加 Sleep 模块
使用 Tools > Sleep 模块:
- 延迟时间:
{{wait_seconds}}秒
步骤 4:克隆原始模块并重试
- 复制(克隆)原始 API 调用模块
- 将克隆模块放在 Sleep 之后
- 克隆模块成功时,使用 Resume 指令将结果传回主路径
4.4 指数退避提示词模板
你是一个 n8n 工作流错误处理专家。请为以下场景设计指数退避重试逻辑:
**场景描述**:[描述你的 API 调用场景,例如"调用 OpenAI API 生成文章摘要"]
**预期错误类型**:[例如"429 速率限制、500 服务器错误、网络超时"]
**最大重试次数**:[例如 5]
**基础延迟**:[例如 1 秒]
**最大延迟**:[例如 60 秒]
请提供:
1. 完整的 n8n 节点拓扑图(ASCII 格式)
2. Code 节点中的退避时间计算代码
3. 各节点的关键配置参数
4. 重试耗尽后的处理建议5. 死信队列(Dead Letter Queue)
5.1 死信队列概念
死信队列(DLQ)是一种消息处理模式:当消息经过多次重试仍无法成功处理时,将其转移到一个专门的”死信”存储中,供后续人工审查或自动重放。
在 n8n/Make.com 的上下文中,“死信”指的是经过所有重试和降级策略后仍然失败的工作流执行数据。
5.2 n8n 死信队列实现
n8n 没有内置的死信队列功能,但可以通过以下模式实现:
架构设计
[生产工作流]
→ [节点执行失败]
→ [重试耗尽]
→ [Error Output / Error Workflow]
→ [DLQ Writer: 写入死信队列]
├── 存储到 Postgres/Supabase 表
├── 或存储到 Google Sheets
└── 或存储到 Redis List
[DLQ 处理工作流](独立工作流)
→ [Schedule Trigger: 定时扫描]
→ [读取 DLQ 中的待处理项]
→ [逐项重试]
├── ✅ 成功 → 标记为已处理
└── ❌ 失败 → 更新重试计数
├── 未超限 → 保留在队列中
└── 已超限 → 标记为永久失败 + 告警操作步骤
步骤 1:创建死信队列存储表
使用 Supabase/PostgreSQL:
CREATE TABLE dead_letter_queue (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
workflow_id VARCHAR(50) NOT NULL,
workflow_name VARCHAR(255) NOT NULL,
execution_id VARCHAR(50),
node_name VARCHAR(255),
error_message TEXT,
error_code VARCHAR(20),
input_data JSONB,
retry_count INTEGER DEFAULT 0,
max_retries INTEGER DEFAULT 5,
status VARCHAR(20) DEFAULT 'pending', -- pending, retrying, resolved, dead
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
next_retry_at TIMESTAMP,
resolved_at TIMESTAMP
);
-- 索引优化
CREATE INDEX idx_dlq_status ON dead_letter_queue(status);
CREATE INDEX idx_dlq_next_retry ON dead_letter_queue(next_retry_at)
WHERE status = 'pending';
CREATE INDEX idx_dlq_workflow ON dead_letter_queue(workflow_id);步骤 2:在错误处理中写入 DLQ
在 Error Workflow 或 Error Output 分支中添加 Postgres 节点:
INSERT INTO dead_letter_queue
(workflow_id, workflow_name, execution_id, node_name,
error_message, error_code, input_data, next_retry_at)
VALUES
($1, $2, $3, $4, $5, $6, $7::jsonb,
NOW() + INTERVAL '15 minutes')参数映射:
$1:{{ $json.workflow.id }}$2:{{ $json.workflow.name }}$3:{{ $json.execution.id }}$4:{{ $json.execution.lastNodeExecuted }}$5:{{ $json.execution.error.message }}$6:{{ $json.execution.error.httpCode || 'UNKNOWN' }}$7:{{ JSON.stringify($json.originalInput || {}) }}
步骤 3:创建 DLQ 处理工作流
[Schedule Trigger: 每 15 分钟]
→ [Postgres: 查询待处理项]
SELECT * FROM dead_letter_queue
WHERE status = 'pending'
AND next_retry_at <= NOW()
ORDER BY created_at ASC
LIMIT 10
→ [Loop Over Items]
→ [Code: 构建重试请求]
→ [HTTP Request: 重试原始操作]
├── ✅ 成功
│ → [Postgres: UPDATE status='resolved', resolved_at=NOW()]
└── ❌ 失败
→ [Code: 更新重试计数和下次重试时间]
→ [IF: retry_count >= max_retries?]
├── 是 → [Postgres: UPDATE status='dead']
│ → [Slack: 永久失败告警]
└── 否 → [Postgres: UPDATE retry_count, next_retry_at]步骤 4:DLQ 重试时间计算
// Code 节点:计算下次重试时间(指数退避)
const retryCount = $input.item.json.retry_count + 1;
const baseMinutes = 15;
const maxMinutes = 1440; // 24 小时
const delayMinutes = Math.min(
baseMinutes * Math.pow(2, retryCount - 1),
maxMinutes
);
const nextRetryAt = new Date(
Date.now() + delayMinutes * 60 * 1000
).toISOString();
return {
...$input.item.json,
retry_count: retryCount,
next_retry_at: nextRetryAt,
delay_minutes: delayMinutes
};5.3 Make.com 死信队列实现
Make.com 的 Incomplete Executions(不完整执行)功能本身就是一种内置的死信队列机制。
内置方案:Incomplete Executions
工作原理:
- 当使用 Break 指令时,失败的执行数据会被存入 Incomplete Executions 队列
- 可以配置自动重试(次数和间隔)
- 重试耗尽后,数据保留在队列中供人工处理
- 在 Make.com 控制台的 Incomplete Executions 面板中可以查看、编辑和重放
配置步骤:
- 场景设置 → Allow storing of incomplete executions →
Yes - 场景设置 → Sequential processing →
Yes(推荐,避免并发冲突) - 在需要 DLQ 的模块上添加 Error Handler → Break
- 配置 Break 的自动重试参数
限制:
- 不完整执行有存储上限(取决于 Make.com 套餐)
- 自动重试间隔是固定的,不支持指数退避
- 无法通过 API 批量管理不完整执行
增强方案:外部 DLQ 存储
对于需要更灵活控制的场景,可以将失败数据写入外部存储:
[目标模块]
│
└── ❌ Error Handler
→ [HTTP Request: POST 到 Supabase/Airtable]
Body: {
"scenario_id": "{{scenario.id}}",
"scenario_name": "{{scenario.name}}",
"error": "{{error.message}}",
"input_data": {{toJSON(input)}},
"timestamp": "{{now}}"
}
→ [Slack: 发送 DLQ 入队通知]
→ [Ignore](让场景继续处理其他项目)5.4 DLQ 监控仪表板
关键指标
| 指标 | 计算方式 | 告警阈值 | 说明 |
|---|---|---|---|
| 队列深度 | COUNT WHERE status=‘pending’ | > 50 | 积压过多需要关注 |
| 入队速率 | 过去 1 小时新增数量 | > 20/小时 | 可能存在系统性问题 |
| 平均处理时间 | AVG(resolved_at - created_at) | > 4 小时 | 处理效率下降 |
| 永久失败率 | COUNT(status=‘dead’) / COUNT(*) | > 10% | 需要排查根因 |
| 重试成功率 | COUNT(status=‘resolved’) / COUNT(retried) | < 60% | 重试策略可能需要调整 |
监控查询示例
-- DLQ 概览仪表板
SELECT
status,
COUNT(*) as count,
AVG(retry_count) as avg_retries,
MIN(created_at) as oldest_item,
MAX(created_at) as newest_item
FROM dead_letter_queue
WHERE created_at >= NOW() - INTERVAL '24 hours'
GROUP BY status;
-- 按工作流分组的失败统计
SELECT
workflow_name,
COUNT(*) as total_failures,
COUNT(*) FILTER (WHERE status = 'resolved') as resolved,
COUNT(*) FILTER (WHERE status = 'dead') as permanent_failures,
ROUND(
COUNT(*) FILTER (WHERE status = 'resolved')::numeric /
NULLIF(COUNT(*), 0) * 100, 1
) as resolution_rate_pct
FROM dead_letter_queue
WHERE created_at >= NOW() - INTERVAL '7 days'
GROUP BY workflow_name
ORDER BY total_failures DESC;6. 降级路径(Fallback / Degradation Path)
6.1 降级策略概览
降级路径是指当主要服务不可用时,自动切换到备用方案以保证工作流继续运行(可能以降低的质量或功能)。
| 降级策略 | 描述 | 质量影响 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 模型降级 | 主模型不可用时切换到备用模型 | 中 | 低 | LLM 调用 |
| 提供商降级 | 主提供商不可用时切换到备用提供商 | 低 | 中 | API 调用 |
| 缓存降级 | 使用缓存的历史结果 | 中高 | 中 | 数据查询、AI 生成 |
| 默认值降级 | 使用预设的默认值 | 高 | 低 | 非关键字段填充 |
| 功能降级 | 跳过非关键功能步骤 | 中 | 低 | 可选增强步骤 |
| 人工降级 | 转交人工处理 | 无 | 中 | 关键业务决策 |
6.2 n8n 降级路径实现
模式一:LLM 模型降级链
当主 LLM 不可用时,自动切换到备用模型:
[输入数据]
→ [OpenAI: GPT-4.1](主模型)
├── ✅ 成功 → [输出验证] → [后续处理]
└── ❌ Error Output
→ [Anthropic: Claude Sonnet](备用模型 1)
├── ✅ 成功 → [输出验证] → [后续处理]
└── ❌ Error Output
→ [Google AI: Gemini 2.5 Flash](备用模型 2)
├── ✅ 成功 → [输出验证] → [后续处理]
└── ❌ Error Output
→ [缓存/默认值降级] → [后续处理(标记降级)]操作步骤
步骤 1:配置主模型节点
- 添加 OpenAI 节点(或 HTTP Request 调用 OpenAI API)
- 设置 On Error 为
Output Error Data - 配置 Retry on Fail:2 次,间隔 3000ms
步骤 2:配置备用模型节点
- 从主模型的错误输出连接 Anthropic 节点
- 使用相同的 prompt(通过表达式引用原始输入)
- 同样设置 On Error 为
Output Error Data
步骤 3:配置最终降级
// Code 节点:最终降级逻辑
const originalInput = $input.item.json.originalText || '';
// 使用简单的文本截取作为降级摘要
const fallbackSummary = originalInput.substring(0, 200) + '...';
return {
summary: fallbackSummary,
model_used: 'fallback_truncation',
is_degraded: true,
degradation_reason: 'All LLM providers unavailable',
timestamp: new Date().toISOString()
};步骤 4:在后续处理中标记降级状态
// 在输出中添加降级标记
const isDegraded = $input.item.json.is_degraded || false;
const modelUsed = $input.item.json.model_used || 'gpt-4.1';
return {
...$input.item.json,
metadata: {
model_used: modelUsed,
is_degraded: isDegraded,
processed_at: new Date().toISOString()
}
};模式二:缓存降级
[输入数据]
→ [Code: 生成缓存键]
→ [Redis/Supabase: 查询缓存]
→ [IF: 缓存命中?]
├── 是 → [使用缓存数据](跳过 API 调用)
└── 否 → [API 调用]
├── ✅ 成功 → [Redis/Supabase: 写入缓存] → [后续处理]
└── ❌ 失败 → [使用默认值] → [后续处理(标记降级)]模式三:功能降级(跳过非关键步骤)
[核心数据处理]
→ [AI 情感分析](可选增强)
├── ✅ 成功 → [合并结果]
└── ❌ Error Output → [Set: sentiment = "unknown"] → [合并结果]
→ [AI 关键词提取](可选增强)
├── ✅ 成功 → [合并结果]
└── ❌ Error Output → [Set: keywords = []] → [合并结果]
→ [写入数据库](核心操作,不降级)6.3 Make.com 降级路径实现
使用 Resume 指令实现降级
[OpenAI: 生成摘要]
├── ✅ 成功 → [后续处理]
└── ❌ Error Handler
→ [Router]
├── 路径 1:尝试备用模型
│ → [HTTP Request: Anthropic API]
│ ├── ✅ 成功 → [Resume](使用备用结果)
│ └── ❌ Error Handler → [路径 2]
└── 路径 2:使用默认值
→ [Text: 截取原文前200字]
→ [Resume](使用默认值)使用 Router 实现条件降级
[HTTP Request: 主 API]
│
└── ❌ Error Handler
→ [Set Variable: error_type = {{error.code}}]
→ [Router]
├── 条件:error_type = 429(速率限制)
│ → [Sleep: 30秒] → [HTTP Request: 重试] → [Resume]
├── 条件:error_type >= 500(服务端错误)
│ → [HTTP Request: 备用 API] → [Resume]
└── 默认路径
→ [Slack: 告警] → [Ignore]6.4 降级路径提示词模板
你是一个 [n8n/Make.com] 工作流架构师。请为以下 AI 工作流设计完整的降级路径:
**工作流描述**:[描述你的工作流,例如"从 RSS 抓取文章 → AI 生成摘要 → 翻译 → 发布到 Slack"]
**关键节点**:[列出不能失败的核心节点]
**可降级节点**:[列出可以降级的非关键节点]
**可用的备用服务**:[例如"备用 LLM: Claude, Gemini; 备用通知: Email"]
请提供:
1. 每个可降级节点的降级策略(模型降级/缓存降级/默认值降级)
2. 降级后的数据标记方案(如何标识降级数据)
3. 降级触发条件和恢复条件
4. 完整的节点拓扑图7. 告警系统(Alerting)
7.1 告警分级体系
| 级别 | 名称 | 触发条件 | 通知渠道 | 响应时间 |
|---|---|---|---|---|
| P0 | 🔴 紧急 | 核心业务流程完全中断 | PagerDuty + 电话 + Slack | < 15 分钟 |
| P1 | 🟠 严重 | 关键功能降级或部分不可用 | Slack #alerts-critical + 邮件 | < 1 小时 |
| P2 | 🟡 警告 | 非关键功能异常或性能下降 | Slack #alerts-warning | < 4 小时 |
| P3 | 🟢 信息 | 可预期的错误或需要关注的趋势 | 日志记录 + 周报汇总 | 下个工作日 |
7.2 n8n 告警实现
方案一:集中式 Error Workflow 告警
这是最推荐的方案,所有工作流共享一个告警工作流:
[Error Trigger]
→ [Code: 错误分级 + 去重检查]
→ [Switch: 按严重度路由]
├── P0 紧急
│ → [PagerDuty: 创建事件]
│ → [Slack: #alerts-critical(@channel)]
│ → [Postgres: 记录错误日志]
├── P1 严重
│ → [Slack: #alerts-critical]
│ → [Gmail: 发送告警邮件]
│ → [Postgres: 记录错误日志]
├── P2 警告
│ → [Slack: #alerts-warning]
│ → [Postgres: 记录错误日志]
└── P3 信息
→ [Postgres: 记录错误日志]告警去重逻辑
避免同一错误在短时间内产生大量重复告警:
// Code 节点:告警去重
const error = $input.item.json.execution.error;
const workflow = $input.item.json.workflow;
// 生成去重键:工作流ID + 错误消息的哈希
const dedupeKey = `${workflow.id}_${error.message}`.substring(0, 100);
// 检查最近 30 分钟内是否已发送过相同告警
const staticData = $getWorkflowStaticData('global');
const now = Date.now();
const cooldownMs = 30 * 60 * 1000; // 30 分钟冷却期
if (staticData[dedupeKey] && (now - staticData[dedupeKey]) < cooldownMs) {
// 在冷却期内,跳过告警但仍记录日志
return {
...formatError($input.item.json),
alert_suppressed: true,
suppression_reason: 'Duplicate within cooldown period'
};
}
// 更新最后告警时间
staticData[dedupeKey] = now;
// 清理过期的去重记录(避免内存泄漏)
for (const key of Object.keys(staticData)) {
if (staticData[key] < now - cooldownMs * 2) {
delete staticData[key];
}
}
return {
...formatError($input.item.json),
alert_suppressed: false
};
function formatError(data) {
const error = data.execution.error;
const workflow = data.workflow;
let severity = 'P3';
let emoji = '🟢';
// P0:支付、认证、数据库连接
if (
workflow.name.match(/payment|billing|order/i) ||
(error.message && error.message.match(/ECONNREFUSED|connection.*refused/i))
) {
severity = 'P0';
emoji = '🔴';
}
// P1:认证过期、持续性 5xx
else if (
error.message && error.message.match(/401|403|unauthorized/i)
) {
severity = 'P1';
emoji = '🟠';
}
// P2:速率限制、超时
else if (
error.message && error.message.match(/429|timeout|ETIMEDOUT|rate.?limit/i)
) {
severity = 'P2';
emoji = '🟡';
}
return {
severity,
emoji,
workflow_name: workflow.name,
workflow_id: workflow.id,
error_message: error.message || 'Unknown error',
execution_id: data.execution.id,
execution_url: data.execution.url,
timestamp: new Date().toISOString()
};
}方案二:定时错误汇总报告
除了实时告警,还应该有定期的错误汇总报告:
[Schedule Trigger: 每天 09:00]
→ [Postgres: 查询过去 24 小时错误统计]
→ [Code: 生成报告]
→ [Slack: 发送每日错误摘要]每日错误报告查询
SELECT
workflow_name,
COUNT(*) as error_count,
COUNT(DISTINCT error_message) as unique_errors,
MIN(created_at) as first_occurrence,
MAX(created_at) as last_occurrence,
ARRAY_AGG(DISTINCT error_code) as error_codes
FROM dead_letter_queue
WHERE created_at >= NOW() - INTERVAL '24 hours'
GROUP BY workflow_name
ORDER BY error_count DESC
LIMIT 20;报告格式模板
📊 *每日工作流错误报告*
📅 ${date}
*概览*
• 总错误数:${totalErrors}
• 受影响工作流:${affectedWorkflows}
• 已自动恢复:${autoResolved}(${autoResolvedPct}%)
• 需要人工处理:${manualRequired}
*Top 5 错误工作流*
${top5List}
*DLQ 状态*
• 队列深度:${queueDepth}
• 永久失败:${deadCount}
• 待重试:${pendingCount}
<${dashboardUrl}|查看完整仪表板>7.3 Make.com 告警实现
内置告警功能
Make.com 提供内置的执行通知:
- 进入场景设置
- Notifications 部分:
- Warning 通知:场景执行出现警告时发送
- Error 通知:场景执行失败时发送
- 通知会发送到 Make.com 账户关联的邮箱
自定义告警增强
内置通知功能有限,建议在错误路由中添加自定义告警:
[目标模块]
│
└── ❌ Error Handler
→ [Slack: 发送告警]
Channel: #automation-alerts
Message: "⚠️ 场景 '{{scenario.name}}' 执行失败
模块: {{failedModule.name}}
错误: {{error.message}}
时间: {{formatDate(now; 'YYYY-MM-DD HH:mm:ss')}}"
→ [Google Sheets: 记录错误日志]
Spreadsheet: "Automation Error Log"
Row: [{{now}}, {{scenario.name}}, {{error.message}}, {{error.type}}]
→ [Break / Resume / Ignore]7.4 告警通知渠道配置
Slack 告警配置(n8n)
步骤 1:创建 Slack App 和 Webhook
- 访问 api.slack.com/apps
- 创建新 App → From scratch
- 启用 Incoming Webhooks
- 添加 Webhook 到目标频道
- 复制 Webhook URL
步骤 2:在 n8n 中配置 Slack 节点
- 添加 Slack 节点
- 配置凭证(OAuth 或 Webhook)
- 设置消息格式:
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "${emoji} 工作流错误告警 [${severity}]"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*工作流:*\n${workflow_name}"
},
{
"type": "mrkdwn",
"text": "*严重度:*\n${severity}"
},
{
"type": "mrkdwn",
"text": "*错误信息:*\n${error_message}"
},
{
"type": "mrkdwn",
"text": "*时间:*\n${timestamp}"
}
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "查看执行详情"
},
"url": "${execution_url}"
}
]
}
]
}邮件告警配置
对于 P0/P1 级别的告警,建议同时发送邮件:
[Switch: P0 或 P1]
→ [Gmail: 发送告警邮件]
To: [ops-team@company.com]
Subject: "[${severity}] 工作流告警: ${workflow_name}"
Body: "
工作流 '${workflow_name}' 执行失败。
错误信息:${error_message}
执行 ID:${execution_id}
时间:${timestamp}
请立即查看:${execution_url}
"PagerDuty 集成(P0 级别)
对于最高优先级的告警,集成 PagerDuty 实现电话/短信通知和值班升级:
[Switch: P0]
→ [HTTP Request: PagerDuty Events API v2]
Method: POST
URL: https://events.pagerduty.com/v2/enqueue
Body: {
"routing_key": "[你的 PagerDuty Integration Key]",
"event_action": "trigger",
"payload": {
"summary": "工作流 '${workflow_name}' 严重故障",
"severity": "critical",
"source": "n8n-automation",
"component": "${workflow_name}",
"custom_details": {
"error_message": "${error_message}",
"execution_url": "${execution_url}"
}
}
}7.5 告警提示词模板
你是一个自动化运维专家。请为以下 [n8n/Make.com] 工作流设计完整的告警体系:
**工作流清单**:
[列出你的生产工作流,例如:
- AI 内容管线(每日运行)
- 线索评分(Webhook 触发)
- 数据同步(每小时运行)]
**团队规模**:[例如"3 人团队"]
**值班制度**:[例如"工作日 9-18 点有人值班"]
**现有通知渠道**:[例如"Slack、邮件、企业微信"]
请提供:
1. 告警分级标准(P0-P3)及每级的触发条件
2. 每级告警的通知渠道和响应时间要求
3. 告警去重和抑制策略
4. 每日/每周错误汇总报告的内容和格式
5. 值班升级流程8. AI 工作流专属错误处理
8.1 LLM 输出验证
AI 工作流中最常见的”隐性错误”是 LLM 输出不符合预期格式。这类错误不会触发 HTTP 错误码,但会导致下游节点解析失败。
常见 LLM 输出问题
| 问题 | 表现 | 检测方法 | 处理策略 |
|---|---|---|---|
| JSON 格式错误 | 输出不是有效 JSON | JSON.parse 尝试解析 | 重试(换 prompt)或正则提取 |
| 字段缺失 | 缺少必需字段 | Schema 验证 | 重试或使用默认值 |
| 内容幻觉 | 生成虚假信息 | 事实核查(可选) | 标记为待审核 |
| 拒绝回答 | ”I cannot…” 类回复 | 关键词检测 | 调整 prompt 重试 |
| 输出过长/过短 | 超出预期长度范围 | 长度检查 | 重试或截断 |
| 语言错误 | 输出语言不符合要求 | 语言检测 | 重试(强调语言要求) |
n8n LLM 输出验证节点
// Code 节点:LLM 输出验证
const llmOutput = $input.item.json.text || $input.item.json.output || '';
const validationResult = {
raw_output: llmOutput,
is_valid: true,
errors: [],
parsed_data: null
};
// 1. 检查是否为空
if (!llmOutput || llmOutput.trim().length === 0) {
validationResult.is_valid = false;
validationResult.errors.push('LLM 输出为空');
return validationResult;
}
// 2. 尝试 JSON 解析(如果期望 JSON 输出)
try {
// 尝试从 markdown 代码块中提取 JSON
const jsonMatch = llmOutput.match(/```(?:json)?\s*([\s\S]*?)```/);
const jsonStr = jsonMatch ? jsonMatch[1].trim() : llmOutput.trim();
validationResult.parsed_data = JSON.parse(jsonStr);
} catch (e) {
validationResult.is_valid = false;
validationResult.errors.push(`JSON 解析失败: ${e.message}`);
}
// 3. 检查必需字段(根据你的 schema 调整)
if (validationResult.parsed_data) {
const required = ['title', 'summary', 'category'];
for (const field of required) {
if (!validationResult.parsed_data[field]) {
validationResult.is_valid = false;
validationResult.errors.push(`缺少必需字段: ${field}`);
}
}
}
// 4. 检查拒绝回答
const refusalPatterns = [
/I (?:cannot|can't|am unable to)/i,
/I'm sorry,? (?:but )?I/i,
/as an AI/i,
/I don't have (?:access|the ability)/i
];
for (const pattern of refusalPatterns) {
if (pattern.test(llmOutput)) {
validationResult.is_valid = false;
validationResult.errors.push('LLM 拒绝回答');
break;
}
}
// 5. 长度检查
if (llmOutput.length < 50) {
validationResult.errors.push('输出过短(< 50 字符)');
}
if (llmOutput.length > 10000) {
validationResult.errors.push('输出过长(> 10000 字符)');
}
return validationResult;验证失败后的处理流程
[LLM 调用]
→ [Code: 输出验证]
→ [IF: is_valid?]
├── ✅ 有效 → [后续处理]
└── ❌ 无效 → [Code: 构建修正 prompt]
→ [LLM 调用: 重试(含修正指令)]
→ [Code: 二次验证]
→ [IF: 二次验证通过?]
├── ✅ 通过 → [后续处理]
└── ❌ 失败 → [降级处理 / DLQ]8.2 AI Agent 循环超时保护
n8n 的 AI Agent 节点可能陷入无限循环(Agent 反复调用工具但无法完成任务)。需要设置超时保护:
// 在 AI Agent 节点的 System Message 中添加超时约束
const systemMessage = `
你是一个高效的 AI 助手。请遵守以下约束:
1. 最多使用 5 次工具调用来完成任务
2. 如果 3 次工具调用后仍无法完成,请返回当前最佳结果并说明原因
3. 不要重复调用相同的工具(除非参数不同)
4. 如果遇到错误,最多重试 2 次,然后报告错误
`;Agent 执行监控
[AI Agent 节点]
→ [Code: 检查执行统计]
// 检查 Agent 的工具调用次数和执行时间
const agentOutput = $input.item.json;
const toolCalls = agentOutput.steps?.length || 0;
if (toolCalls > 10) {
// Agent 可能陷入循环
return {
warning: 'Agent 工具调用次数过多',
tool_calls: toolCalls,
needs_review: true
};
}8.3 速率限制智能管理
n8n 速率限制处理模式
[批量数据输入]
→ [Split In Batches: 每批 5 个]
→ [Wait: 1 秒(批次间延迟)]
→ [HTTP Request: API 调用]
├── ✅ 成功 → [收集结果]
└── ❌ Error Output
→ [IF: 是 429 错误?]
├── 是 → [Code: 解析 Retry-After 头]
│ → [Wait: 动态等待]
│ → [HTTP Request: 重试]
└── 否 → [其他错误处理]解析 Retry-After 头
// Code 节点:解析速率限制响应
const error = $input.item.json.error;
const headers = error.headers || {};
// 尝试从 Retry-After 头获取等待时间
let waitSeconds = 60; // 默认等待 60 秒
if (headers['retry-after']) {
const retryAfter = headers['retry-after'];
if (/^\d+$/.test(retryAfter)) {
waitSeconds = parseInt(retryAfter);
} else {
// Retry-After 可能是日期格式
const retryDate = new Date(retryAfter);
waitSeconds = Math.max(
Math.ceil((retryDate.getTime() - Date.now()) / 1000),
1
);
}
}
// 添加 10% 的安全余量
waitSeconds = Math.ceil(waitSeconds * 1.1);
return {
wait_seconds: waitSeconds,
original_error: error.message
};实战案例:生产级 AI 内容管线的完整错误处理
案例背景
构建一个每日自动运行的 AI 内容管线:从 RSS 源抓取文章 → AI 生成中文摘要 → 发布到 Slack 和 Notion。需要处理各种可能的故障场景。
完整工作流拓扑
[Schedule Trigger: 每天 08:00]
→ [RSS Feed Read: 多源抓取]
├── ✅ 成功 → [Merge + Filter: 去重]
└── ❌ Error Output → [Slack: RSS 源不可用告警] → [Stop]
→ [Loop Over Items]
→ [Code: 幂等性检查(跳过已处理文章)]
→ [IF: 已处理?]
├── 是 → [跳过]
└── 否 →
[OpenAI: 生成摘要](Retry: 3次, 5000ms)
├── ✅ 成功 → [Code: 输出验证]
│ ├── ✅ 有效 → [继续]
│ └── ❌ 无效 → [Anthropic: 备用模型重试]
│ ├── ✅ 成功 → [继续]
│ └── ❌ 失败 → [Code: 文本截取降级] → [继续(标记降级)]
└── ❌ Error Output
→ [Switch: 错误分类]
├── 429 → [Code: 指数退避] → [Wait] → [重试]
├── 401 → [Slack: 认证过期告警] → [DLQ 写入]
└── 其他 → [Anthropic: 备用模型]
├── ✅ 成功 → [继续]
└── ❌ 失败 → [DLQ 写入]
→ [Slack: 发送摘要](On Error: Output Error Data)
├── ✅ 成功 → [继续]
└── ❌ Error Output → [Log: Slack 发送失败] → [继续(不阻塞)]
→ [Notion: 创建页面](On Error: Output Error Data)
├── ✅ 成功 → [继续]
└── ❌ Error Output → [DLQ 写入]
→ [Code: 标记为已处理]
→ [Code: 生成执行摘要]
→ [Slack: 发送每日处理报告]
[Error Workflow: 全局告警]
→ [Error Trigger]
→ [Code: 错误分级 + 去重]
→ [Switch: 路由]
├── P0/P1 → [Slack: #alerts-critical] + [邮件]
└── P2/P3 → [Slack: #alerts-warning] + [日志]
[DLQ 处理工作流: 每 30 分钟]
→ [Schedule Trigger]
→ [Postgres: 查询待重试项]
→ [Loop: 逐项重试]
→ [更新状态]案例分析
关键设计决策
- 三层防御:节点重试(第一层)→ 模型降级(第二层)→ 死信队列(第三层)
- 非关键步骤不阻塞:Slack 发送失败不影响 Notion 写入和后续处理
- 幂等性保证:通过文章 URL 哈希去重,避免重复处理
- 降级标记:降级数据会被标记,方便后续人工审查
- 集中式告警:所有工作流共享一个 Error Workflow,统一管理告警逻辑
- DLQ 自动重试:失败数据不会丢失,定时自动重试
错误处理覆盖率
| 故障场景 | 处理方式 | 数据是否丢失 |
|---|---|---|
| RSS 源不可用 | 告警 + 终止 | 否(下次执行会抓取) |
| OpenAI API 429 | 指数退避重试 | 否 |
| OpenAI API 不可用 | 切换到 Anthropic | 否 |
| 所有 LLM 不可用 | 文本截取降级 | 否(降级质量) |
| Slack 发送失败 | 记录日志,继续执行 | 摘要不丢失 |
| Notion 写入失败 | 写入 DLQ,稍后重试 | 否 |
| 认证过期 | 紧急告警 + DLQ | 否 |
避坑指南
❌ 常见错误
-
没有任何错误处理就上线生产
- 问题:工作流静默失败,数据丢失,直到用户投诉才发现
- 正确做法:至少配置一个全局 Error Workflow 发送 Slack/邮件告警,这是最低限度的生产要求
-
对非幂等操作启用无限重试
- 问题:邮件发送、支付扣款等操作被重复执行,导致用户收到多封邮件或被多次扣款
- 正确做法:对非幂等操作限制重试次数(最多 1-2 次),并在业务逻辑中加入去重机制(如唯一请求 ID)
-
重试间隔设置为固定值且过短
- 问题:当 API 返回 429 时,固定 1 秒间隔的重试会持续触发速率限制,形成”重试风暴”
- 正确做法:使用指数退避策略,并解析
Retry-After响应头来确定等待时间
-
忽略 LLM 输出验证
- 问题:LLM 返回格式错误的 JSON 或拒绝回答,下游节点解析失败导致整个工作流崩溃
- 正确做法:在 LLM 调用后添加输出验证节点,检查 JSON 格式、必需字段、拒绝回答等
-
告警没有去重和分级
- 问题:一个 API 持续返回 500 错误,每分钟触发一次告警,团队被告警淹没后开始忽略所有告警
- 正确做法:实现告警去重(相同错误 30 分钟内只告警一次)和分级(P0-P3),避免告警疲劳
-
Make.com 中不了解 Break 和 Ignore 的区别
- 问题:对关键操作使用 Ignore 导致数据丢失,或对非关键操作使用 Break 导致不完整执行队列堆积
- 正确做法:关键操作用 Break(保留数据可重试),非关键操作用 Ignore(跳过不影响主流程)
-
死信队列没有监控和清理机制
- 问题:DLQ 中的数据越积越多,永远没人处理,最终变成数据黑洞
- 正确做法:设置 DLQ 深度告警(超过阈值时通知),配置自动重试,定期清理永久失败的记录
-
降级路径没有标记降级状态
- 问题:使用默认值或截取文本替代 AI 生成结果,但下游无法区分正常数据和降级数据
- 正确做法:在降级数据中添加
is_degraded: true和degradation_reason字段,方便后续审查和统计
✅ 最佳实践
- 遵循”洋葱模型”:从内到外依次配置节点重试 → 错误输出分支 → 全局 Error Workflow,形成多层防御
- 区分瞬时故障和持久故障:瞬时故障(网络抖动、429)自动重试,持久故障(认证过期、API 变更)立即告警
- 为每个生产工作流配置 Error Workflow:这是最基本的生产要求,确保任何未捕获的错误都能被发现
- 使用指数退避而非固定间隔重试:避免重试风暴,对 API 提供商友好
- 实现死信队列:确保失败数据不丢失,可以在问题修复后重放
- 定期审查 DLQ 和错误日志:每周审查一次,识别系统性问题和趋势
- 测试错误处理路径:不要只测试正常路径,主动模拟各种故障场景验证错误处理是否正常工作
- 记录错误处理决策:在工作流的 Notes 中记录为什么选择某种错误处理策略,方便团队成员理解和维护
相关资源与延伸阅读
官方文档
- n8n Error Handling 官方文档 — n8n 错误处理机制的权威参考,覆盖 Error Trigger、Error Workflow、Retry on Fail 等所有内置功能
- Make.com Error Handling 官方文档 — Make.com 五大错误处理指令(Rollback、Commit、Ignore、Resume、Break)的官方说明
- Make.com Directives for Error Handling — Make.com 错误处理指令的快速参考表
社区模板与工作流
- n8n 官方工作流模板库 — 搜索 “error handling” 可找到多个社区贡献的错误处理工作流模板,包括 Slack 告警、日志记录等
- n8n 指数退避工作流模板 — 针对 Google API 的指数退避重试模板,可适配其他 API
- n8n 错误监控 + Slack 告警模板 — 每小时自动检查失败执行并发送 Slack 汇总告警
深度教程
- 5 n8n Error Handling Techniques for Resilient Workflows — 覆盖 Error Workflow、重试、指数退避、批处理错误隔离等 5 种核心技术的深度教程
- Advanced n8n Error Handling and Recovery Strategies — 高级错误恢复策略,包括故障模式映射、断路器模式等
- Make.com Error Handling Best Practices — Make.com 错误处理最佳实践详解
相关概念
- Idempotency, Retries & Dead-Letter Queues (Practical Guide) — 幂等性、重试和死信队列的通用工程实践指南
- Reconciliation Loops with Exponential Backoff — 指数退避在控制器协调循环中的应用
监控与告警工具
- PagerDuty Events API v2 — PagerDuty 事件 API 文档,用于集成自动化告警升级
- Grafana 告警规则配置 — Grafana 告警系统文档,适合构建工作流健康度仪表板
参考来源
- n8n 官方文档:Error Handling (持续更新)
- Make.com 官方文档:Introduction to Error Handling (持续更新)
- Make.com 官方文档:Directives for Error Handling (持续更新)
- 5 n8n Error Handling Techniques for Resilient Workflows — AI Fire (2025-08)
- Advanced n8n Error Handling and Recovery Strategies — Wednesday Solutions (2025-06)
- Advanced Error Handling for n8n AI Workflows — Value Added Tech (2025-06)
- Handle API Rate Limits in n8n — Logic Workflow (2025-06)
- Webhooks That Don’t Fall Over: 5 n8n Reliability Patterns — Roland Softwares (2025-12)
- Best Practices for Error Handling in Make.com — Medium (2024-11)
- Break Error Handling Demystified — The AI Automators (2025-01)
- Idempotency, Retries & Dead-Letter Queues — Scalable Code (2026-02)
- Reconciliation Loops with Exponential Backoff — OneUptime (2026-02)
- n8n Error Trigger Node Tutorial — Logic Workflow (2025-06)
- n8n Error Handling, Debugging & Security — Codesmith (2025-12)
📖 返回 总览与导航 | 上一节:26c-工作流蓝图集 | 下一节:26e-Webhook与高级集成