单个工作流解决单个问题,这是入门阶段。当你手里的 workflow 多了之后,会开始遇到一类问题:同样的逻辑在三个 workflow 里各写了一遍;某个 workflow 失败了但 error 通知没覆盖到;API Key 被写死在节点里,换人负责时不知道密钥在哪里。
这章不讲新的集成,讲三件让 n8n 项目真正可维护的架构决策:sub-workflow 模块化、error workflow 设计模式、credentials 治理。
---
Sub-workflow:把重复逻辑抽出来
为什么需要 sub-workflow
最直接的场景:发 Slack 告警 这件事,你的日报 workflow、订单监控 workflow、数据同步 workflow 都要干。最简单的做法是每个 workflow 里复制一段 Slack 节点,但这意味着 Slack Channel ID 或消息格式一变,你要改三个地方——而且很可能忘掉其中一个。
Sub-workflow 的思路是把"发 Slack"这个逻辑封装成一个独立 workflow,其他 workflow 通过 Execute Sub-workflow 节点调用它,就像调用函数一样。
另一个常见场景是批量处理:主 workflow 把 1000 条记录切成 50 条一批,对每批调用 sub-workflow 处理,子流处理完把结果返回给主流合并。这比在单个 workflow 里写 Split In Batches + 复杂逻辑要清晰得多。
两个节点:Execute Sub-workflow + Execute Sub-workflow Trigger
Sub-workflow 涉及两个节点:
主 workflow 子 workflow
───────────────────────── ─────────────────────────────────
... 上游节点 ... [Execute Sub-workflow Trigger]
↓ ↓
[Execute Sub-workflow] ------→ ... 子流逻辑 ...
↑ ↓
... 接收返回数据 ... [最后一个节点的输出 = 返回值]Execute Sub-workflow Trigger 是子 workflow 的入口,替代普通 workflow 里的 Webhook / Schedule Trigger。它有三种输入数据模式:
| 模式 | 适用场景 | |------|----------| | Accept all data | 子流只是个工具,不在乎入参结构 | | Define using fields below | 明确要求调用方传哪些字段(有类型校验) | | Define using JSON example | 粘一段示例 JSON,n8n 自动推断字段类型 |
推荐用 Define using fields below 或 Define using JSON example,这样在父 workflow 的 Execute Sub-workflow 节点里,参数面板会自动出现对应的输入字段,不用靠注释或文档告诉别人"你得传什么"。
调用模式:一次全部 vs 逐条 vs 分批
Execute Sub-workflow 节点有三种执行模式,选错会导致行为完全不同:
Run once with all items(默认):把父流当前所有 item 打包成一个数组传给子流,子流只运行一次。适合子流内部自己处理数组的情况(比如拼一封包含多行数据的邮件)。
Run once for each item:父流有多少个 item,子流就跑多少次,每次只传一个 item。适合"对每条记录独立处理"的场景,等效于 forEach。这种模式下并发取决于 n8n 实例配置(自托管可以通过 EXECUTIONS_PROCESS 和 EXECUTIONS_CONCURRENCY_LIMIT 控制)。
Run once for a batch of items:介于两者之间,可以设置 batch size,每批调用一次子流。适合子流有 API 限速要求的场景(比如每次最多 100 条)。
父流有 300 条记录:Run once with all items → 子流跑 1 次,收到 300 条
Run once for each item → 子流跑 300 次,每次 1 条
Batch size=50 → 子流跑 6 次,每次 50 条
子流返回数据
子 workflow 最后一个节点的输出就是返回给父 workflow 的数据——不需要 Respond to Webhook 节点(那个是给 HTTP 调用方用的)。父流里 Execute Sub-workflow 节点的输出 $json 就是子流最后节点输出的 item。
一个完整示例:
[父流] Trigger → 拉取订单列表(300 条)
↓
[父流] Execute Sub-workflow(每条触发一次)
传入: { orderId, amount, customerId }
↓
[子流] Execute Sub-workflow Trigger
↓
[子流] HTTP Request: 查询 CRM 客户详情
↓
[子流] Set: 组合 { orderId, customerName, tier, amount }
↓ ← 子流到这里结束,把 Set 的输出返回父流
[父流] 接收到 300 条增强数据 → 写入数据库子流激活状态的坑
子 workflow 必须是激活状态(Active),父流才能调用它。如果子流没激活,Execute Sub-workflow 节点会直接报错"Workflow is not active"。开发期间测试没问题,上生产忘了激活子流 → 整条链路失败,是常见踩坑点。
---
Error Workflow:从章节六到模块化告警
第六章已经讲了 Error Workflow 的基础配置(建专用 error workflow → Error Trigger → Slack 通知)。这里补三个进阶场景。
子流失败怎么传播
当子 workflow 里有节点失败时:
- 子流执行失败,父流的 Execute Sub-workflow 节点会收到错误
- 父流可以在 Execute Sub-workflow 节点的 Settings 里选 "On Error: Continue"(继续执行,error 信息存在该节点输出的
error字段里) - 如果父流也没捕获这个错误,父流整体失败 → 父流的 Error Workflow 被触发
关键点:子流的 Error Workflow 配置和父流无关。子流里如果配了 error workflow,它只在子流自身失败时触发;父流失败触发的是父流的 error workflow。
实践建议:不要给每个子流单独配 error workflow,只在父流(业务主流)里配。子流保持"dry"——只做计算,失败冒泡给父流处理。
一个 Error Workflow 服务多个业务流
Error Trigger 收到的 $json 包含足够信息来区分是哪个流失败了:
{
"execution": {
"id": "2847",
"url": "https://your-n8n.com/execution/2847",
"retryOf": null,
"error": {
"message": "ETIMEDOUT: connect ETIMEDOUT 1.2.3.4:443",
"stack": "Error: ETIMEDOUT..."
},
"lastNodeExecuted": "HTTP Request"
},
"workflow": {
"id": "45",
"name": "Daily Order Sync"
}
}基于这些字段,你可以在同一个 Error Workflow 里加 Switch 节点,根据 $json.workflow.name 路由到不同的告警渠道(订单流失败发到 #alerts-critical,日报流失败发到 #alerts-low-priority)。这样整个团队只维护一个集中式 error workflow,而不是十几个 workflow 各自配。
[Error Trigger]
↓
[Switch: $json.workflow.name]
├── "Daily Order Sync" → Slack #alerts-critical
├── "Daily Report" → Slack #alerts-low-priority
└── default → Slack #alerts-general防止 Error Workflow 自身失败
Error Workflow 调用了 Slack,如果 Slack API 挂了,Error Workflow 自身失败。n8n 不会递归触发 error workflow(设计上就是这样,避免无限循环),所以这次失败会静默消失。
解决方法:在 Error Workflow 的 Slack 节点后加一个 fallback 路径——把告警同时写进 n8n 自身的 execution log(用 Code 节点 console.error() 打印),或者用 HTTP Request 节点调一个比 Slack 更简单的告警服务(比如自己的 webhook)。
---
Credentials:加密、共享与 dev/prod 分离
Credentials 是 n8n 里最容易被轻视的部分。很多人用 n8n 用了半年,才发现所有 API key 都以"admin"账号为 owner,换人负责时一堆 workflow 的 credentials 没人认领。
加密机制
n8n 用 AES-256 加密所有 credentials,密钥在 ~/.n8n/config 文件里(npm 安装)或通过环境变量 N8N_ENCRYPTION_KEY 指定。
关键操作:自托管的话,第一件事就是把 N8N_ENCRYPTION_KEY 写进环境变量并备份好,不要依赖自动生成的随机密钥。原因:如果服务器重建、Docker volume 丢失、或者你从 npm 安装迁移到 docker 安装,没有原始密钥,数据库里的 credentials 全部无法解密——等于所有连接都要重新配置。
# docker-compose.yml 示例
services:
n8n:
image: n8nio/n8n
environment:
- N8N_ENCRYPTION_KEY=your-32-char-random-string-here
- N8N_USER_MANAGEMENT_JWT_SECRET=another-random-secretCredentials 共享(Cloud/Enterprise)
n8n 的 credentials sharing 遵循最小权限原则:
- 只有 credential 的 owner 或 instance admin 能查看实际密钥值
- 被 share 的用户只能"使用"credential(在 workflow 里选它、执行 workflow),不能读取或复制
- 这意味着你可以把公司的 Slack App token 分享给所有人使用,但没人能看到 token 本身
操作路径:Settings → Credentials → 选择某个 credential → Share → 添加用户或项目(Project)。
Project 是更好的组织单位(n8n v1.x 引入):把相关 workflows 和 credentials 放进同一个 Project,Project 成员自动有该 Project 内所有 credentials 的使用权,不用逐条 share。
命名规范
credentials 没有命名规范的话,几个月后你会看到一堆叫"Slack"、"Slack 2"、"Slack (old)"的 credentials,不知道哪个在用、哪个是废弃的。
推荐格式:{服务}_{环境}_{owner/用途}
Slack_Prod_Alerts # 生产告警频道的 Slack App token
Slack_Dev_Test # 开发测试用的 Slack
OpenAI_Prod_ContentTeam # 内容团队用的 OpenAI key(有独立用量追踪)
OpenAI_Dev # 开发环境共用 key
Notion_Prod_Wiki # 连 Wiki 数据库的 Notion tokenDev / Prod 分离
永远不要在开发和测试 workflow 里使用生产 credentials,这不是安全建议,是运维建议——测试 workflow 跑乱了,可能往真实的客户 Slack Channel 发消息、往真实数据库写垃圾数据。
分离方案分两个级别:
轻量方案(同一个 n8n 实例):
- 每个服务创建两套 credentials(
_Dev和_Prod) - 开发阶段手动用
_Devcredentials - 临上生产时把 workflow 里的 credentials 切换为
_Prod - 用 n8n Variables(
$vars.env)存一个"dev"/"prod"标记,让部分节点根据这个变量自动选分支
标准方案(两个 n8n 实例):
- Dev instance 和 Prod instance 完全隔离,数据库、encryption key 各自独立
- workflow 开发完在 Dev 验证,通过后导出 JSON → 导入 Prod → 重新配 Prod credentials
- 代价是需要维护两套实例,但生产环境的数据安全性大幅提升
External Secrets(Enterprise 功能)
如果团队已经在用 AWS Secrets Manager、HashiCorp Vault 或 Infisical,n8n Enterprise 版本支持直接从这些服务拉取 secrets,不用在 n8n 里单独维护一份:
HashiCorp Vault / AWS Secrets Manager
↓ 实时拉取
n8n Credentials(不在本地 DB 存储明文)
↓
workflow 执行时使用这样 secret rotation(定期换 key)由 Vault/SM 那边统一处理,n8n 侧无需任何操作。
---
三件事的组合
把三个技巧接起来看一个实际架构:
[主流: Daily Data Pipeline]
Schedule Trigger (09:00 AEST)
↓
Execute Sub-workflow: "Fetch Orders" ← 子流负责拉数据
↓
Execute Sub-workflow: "Enrich CRM" ← 子流负责 CRM 查询
↓
Execute Sub-workflow: "Write to DB" ← 子流负责写库
↓
Execute Sub-workflow: "Send Report" ← 子流负责发报告[Error Workflow: Central Alerter]
Error Trigger
↓
Switch by workflow.name
↓
Slack #alerts(带失败 workflow 名、execution URL、错误信息)
[Credentials 结构]
Project: Data Team
- PostgreSQL_Prod_Analytics
- OpenAI_Prod_DataTeam
- Slack_Prod_Alerts
主流只负责调度,子流各自封装一个职责。任意子流失败 → 冒泡到主流 → 主流的 Error Workflow 发告警。Credentials 都在 Data Team Project 下,团队成员有使用权但看不到实际密钥。
这种结构下,加新功能只需要加或改某个子流,不影响整条链路;某个子流需要换 API(比如把 OpenAI 换成 Anthropic),只需要改那个子流的节点和 credentials,其余不动。
---
小结
| 问题 | 对应工具 | |------|----------| | 同样逻辑在多个 workflow 里重复 | Execute Sub-workflow 模块化 | | workflow 失败没有通知 | Error Workflow + Error Trigger | | 多个 workflow 的告警分级 | Switch by workflow.name | | API Key 分散、无法追踪 | Credentials 命名规范 + Project 管理 | | 测试误触生产数据 | Dev/Prod 分离(credentials 或实例级) | | 密钥 rotation 负担 | External Secrets(Vault/AWS SM) |