1. 单机 n8n 撑不住的时候到底卡在哪n8n 单机部署跑几个定时同步、表单通知没问题但一旦工作流里出现大模型调用、批量数据处理、长耗时 HTTP 轮询问题就会集中爆发。最典型的现象是Web UI 点一下「执行工作流」整个界面卡住十几秒其他同事的 Webhook 请求也跟着排队再严重一点容器 OOM 被系统杀掉重启后正在跑的任务全部丢失。根因在于单机模式下n8n 主进程同时承担了四件事提供 Web UI、接收 Webhook、调度任务、执行工作流。这四件事抢同一个 Node.js 事件循环和同一份内存。只要有一个工作流里写了while循环或者同步等待大模型返回整个进程就被拖住。n8n v2.12.x 官方推荐的解法是Queue Mode队列模式把「调度」和「执行」拆开主节点只负责 UI、Webhook 入口和任务编排真正的执行交给独立的 Worker 节点两者通过 Redis 里的 BullMQ 队列通信。再进一步v2.12.x 引入了独立沙盒执行器Runners把大模型生成的 Python/JS 代码放到隔离沙盒里跑避免恶意或失控代码碰主机资源。这套架构适合谁适合已经把 n8n 用在生产环境、任务量开始堆积、或者需要跑 AI Agent 类工作流的团队。如果你还在本地玩单机版可以先收藏等并发上来了再动手。下面我从零开始把从单机迁移到多节点集群的完整路径拆开讲包括可复制的docker-compose.yml、.env、队列配置以及节点宕机、任务重试、健康检查三类验证动作。核心组件分工先理清楚组件角色是否可横向扩展n8nMainWeb UI、Webhook 入口、任务编排调度一般保持 1 个前置负载均衡才多实例n8n-worker从 Redis 队列认领并执行工作流可任意 scalen8n-runner独立沙盒执行 AI 生成的代码可任意 scalePostgreSQL 16存工作流定义、凭证、执行日志主从/托管实例Redis 6BullMQ 队列驱动任务分发与状态同步哨兵/集群理解了这张表后面所有配置都是围绕「让每个组件只干一件事」展开的。2. TaoToken 前置给 Worker 集群接上稳定的大模型出口分布式部署解决的是「算力调度」问题但工作流里真正耗时的大头往往是大模型 API 调用。Worker 扩容到 5 个、10 个之后如果每个 Worker 都直连各家模型厂商会遇到三个麻烦一是密钥散落在多个节点轮换困难二是不同厂商的限流策略不一致某个 Worker 被限流会拖慢整条队列三是网络出口不稳定时重试逻辑会把队列堵死。我的做法是在集群前面加一层统一的大模型接入网关所有 Worker 通过同一个 Base URL 和 Key 调用模型。TaoToken 就是干这个的它提供 OpenAI 兼容的接口n8n 里的 OpenAI 节点、HTTP Request 节点都能直接对接不用改工作流结构。具体来说你需要在 TaoToken 控制台创建一个 API Key然后在 n8n 的凭证管理里配置。这里有个关键点凭证要配在主节点Worker 会自动继承。因为 n8n 的凭证是加密存在 PostgreSQL 里的Worker 从数据库读取解密所以只要主节点配好扩容出来的 Worker 无需重复配置。配置路径分两步。第一步登录控制台拿到 Key打开 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 创建一个新 Key复制保存。这个 Key 后面会填到 n8n 凭证里。第二步在 n8n 里新建凭证。进入 Credentials选择 OpenAI 类型TaoToken 兼容 OpenAI 协议填写Base URLhttps://taotoken.net/api注意不要加 UTM 参数这是接口地址API Key刚才复制的 KeyModel ID按需填写比如gpt-4o、claude-3-5-sonnet等具体可用模型在控制台模型列表里查如果你用的是 HTTP Request 节点直接调那就在 Header 里加Authorization: Bearer 你的KeyURL 写https://taotoken.net/api/v1/chat/completions。为什么要强调这一步因为队列模式下Worker 是「无状态」的它只认队列里的任务和数据库里的凭证。把模型出口统一到网关意味着你扩容 Worker 时不需要在每个节点上配环境变量、不需要分发密钥运维成本直接降一个数量级。而且网关侧可以做统一的限流、重试、日志比在每个 Worker 里各写一套重试逻辑靠谱得多。想先验证模型通不通可以直接用对话页面测一下https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。确认返回正常再往集群里接。3. 可复制的 docker-compose 与队列配置片段这一节是全文最硬的部分所有片段都可以直接复制。我基于 n8n 官方n8n-hosting仓库的withPostgresAndWorker目录做了企业级定制核心改动有三处代理锚点复用、数据修剪防爆盘、Runner 沙盒隔离。先准备目录和脚本git clone https://github.com/n8n-io/n8n-hosting.git cd n8n-hosting/docker-compose/withPostgresAndWorker/ chmod x init-data.shinit-data.sh的作用是首次启动时自动创建 n8n 专属业务库和弱权限账号避免用 postgres 超级用户直连。这一步别跳过否则后面数据库权限会留隐患。接着写.env。我把它拆成四块方便你对照修改# 镜像版本控制 DB_VERSION16.13 N8N_VERSION2.12.1 # PostgreSQL 权限配置 POSTGRES_USERpostgres POSTGRES_PASSWORD换成你的强密码 POSTGRES_DBn8n POSTGRES_NON_ROOT_USERn8n POSTGRES_NON_ROOT_PASSWORD换成你的业务库密码 # 分布式核心安全凭证 # 加密密钥用 openssl rand -hex 16 生成务必备份 ENCRYPTION_KEY你的16位哈希 # Runner 沙盒鉴权令牌用 openssl rand -hex 24 生成 RUNNERS_AUTH_TOKEN你的24位哈希 # 业务增强与数据修剪 N8N_SECURE_COOKIEfalse TZAsia/Shanghai GENERIC_TIMEZONEAsia/Shanghai WEBHOOK_URLhttp://192.168.100.103:5678/ EXECUTIONS_DATA_SAVE_ON_ERRORall EXECUTIONS_DATA_SAVE_ON_SUCCESSnone EXECUTIONS_DATA_PRUNEtrue EXECUTIONS_DATA_MAX_AGE168 N8N_DIAGNOSTICS_ENABLEDfalse N8N_PERSONALIZATION_ENABLEDfalse N8N_HIRING_BANNER_ENABLEDfalse这里有两个坑必须说清楚。WEBHOOK_URL末尾的斜杠不能省少了它 Webhook 回调地址会拼错外部系统收不到通知。ENCRYPTION_KEY一旦丢失数据库里所有第三方凭证GitLab Token、钉钉 Secret全部无法解密等于作废所以生成后立刻存到密码管理器。然后是docker-compose.yml。我用 YAML 锚点把代理配置和公共配置抽出来避免每个服务重复写volumes: db_storage: n8n_storage: redis_storage: x-proxy: proxy NODE_USE_ENV_PROXY: 1 HTTP_PROXY: http://proxy-host-ip:7890 HTTPS_PROXY: http://proxy-host-ip:7890 NO_PROXY: localhost,127.0.0.1,postgres,redis,n8n,n8n-worker,n8n-runner,192.168.0.0/16,10.0.0.0/8,.local,.internal x-shared: shared restart: always image: docker.n8n.io/n8nio/n8n:${N8N_VERSION:-latest} environment: : *proxy DB_TYPE: postgresdb DB_POSTGRESDB_HOST: postgres DB_POSTGRESDB_PORT: 5432 DB_POSTGRESDB_DATABASE: ${POSTGRES_DB} DB_POSTGRESDB_USER: ${POSTGRES_NON_ROOT_USER} DB_POSTGRESDB_PASSWORD: ${POSTGRES_NON_ROOT_PASSWORD} EXECUTIONS_MODE: queue QUEUE_BULL_REDIS_HOST: redis QUEUE_HEALTH_CHECK_ACTIVE: true N8N_ENCRYPTION_KEY: ${ENCRYPTION_KEY} N8N_RUNNERS_MODE: external N8N_RUNNERS_AUTH_TOKEN: ${RUNNERS_AUTH_TOKEN} N8N_RUNNERS_BROKER_LISTEN_ADDRESS: 0.0.0.0 OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS: true GENERIC_TIMEZONE: ${GENERIC_TIMEZONE} TZ: ${TZ} WEBHOOK_URL: ${WEBHOOK_URL} EXECUTIONS_DATA_SAVE_ON_ERROR: ${EXECUTIONS_DATA_SAVE_ON_ERROR} EXECUTIONS_DATA_SAVE_ON_SUCCESS: ${EXECUTIONS_DATA_SAVE_ON_SUCCESS} EXECUTIONS_DATA_PRUNE: ${EXECUTIONS_DATA_PRUNE} EXECUTIONS_DATA_MAX_AGE: ${EXECUTIONS_DATA_MAX_AGE} N8N_DIAGNOSTICS_ENABLED: ${N8N_DIAGNOSTICS_ENABLED} N8N_PERSONALIZATION_ENABLED: ${N8N_PERSONALIZATION_ENABLED} N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE} volumes: - n8n_storage:/home/node/.n8n depends_on: redis: condition: service_healthy postgres: condition: service_healthy x-runner: runner restart: always image: n8nio/runners:${N8N_VERSION:-latest} environment: : *proxy N8N_RUNNERS_AUTH_TOKEN: ${RUNNERS_AUTH_TOKEN} services: postgres: image: postgres:${DB_VERSION:-16} restart: always environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} POSTGRES_NON_ROOT_USER: ${POSTGRES_NON_ROOT_USER} POSTGRES_NON_ROOT_PASSWORD: ${POSTGRES_NON_ROOT_PASSWORD} volumes: - db_storage:/var/lib/postgresql/data - ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh healthcheck: test: [CMD-SHELL, pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}] interval: 5s timeout: 5s retries: 10 redis: image: redis:6-alpine restart: always volumes: - redis_storage:/data healthcheck: test: [CMD, redis-cli, ping] interval: 5s timeout: 5s retries: 10 n8n: : *shared ports: - 5678:5678 n8n-worker: : *shared command: worker depends_on: - n8n n8n-runner: : *runner environment: : *proxy N8N_RUNNERS_AUTH_TOKEN: ${RUNNERS_AUTH_TOKEN} N8N_RUNNERS_TASK_BROKER_URI: http://n8n:5679 depends_on: - n8n n8n-worker-runner: : *runner environment: : *proxy N8N_RUNNERS_AUTH_TOKEN: ${RUNNERS_AUTH_TOKEN} N8N_RUNNERS_TASK_BROKER_URI: http://n8n-worker:5679 depends_on: - n8n-worker几个关键参数解释一下。EXECUTIONS_MODE: queue是开启队列模式的开关没有它 Worker 不会去 Redis 认领任务。QUEUE_HEALTH_CHECK_ACTIVE: true让主节点暴露队列健康检查端点配合负载均衡做存活探测。OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS: true表示手动点击执行也走 Worker避免主节点被手动任务拖住。N8N_RUNNERS_MODE: external启用外部沙盒AI 生成的代码不会在主进程里跑。NO_PROXY白名单是重点postgres、redis、n8n、n8n-worker、n8n-runner这些容器名必须直连内网段10.0.0.0/8、192.168.0.0/16也要排除否则集群内部通信会被错误路由到代理握手直接失败。启动顺序由depends_onhealthcheck保证Postgres 和 Redis 先起健康检查通过后主节点才初始化Worker 和 Runner 再接入队列。这个顺序不能乱否则 Worker 会在数据库没就绪时反复重连。4. 验证请求与成功结果三类动作确认集群真的在跑配置写完不代表集群能用必须做三类验证节点宕机、任务重试、健康检查。我按顺序说。第一类健康检查。启动集群docker compose up -d docker compose ps正常情况下你会看到 postgres、redis、n8n、n8n-worker、n8n-runner、n8n-worker-runner 六个服务都是Up (healthy)。如果某个服务一直starting用docker compose logs 服务名看日志。Redis 的redis-cli ping应该返回PONGPostgres 的pg_isready返回accepting connections。主节点的队列健康检查端点可以直接 curlcurl http://localhost:5678/healthz/readiness返回{status:ok}说明主节点和队列都正常。第二类任务重试。这是队列模式的核心价值。我建一个测试工作流Schedule Trigger 每分钟触发一次接一个 HTTP Request 节点故意请求一个不存在的地址再开重试。在节点设置里把 Retry On Fail 打开Max Tries 设 3Wait Between Tries 设 5000ms。然后观察 Worker 日志docker compose logs -f n8n-worker你会看到 Worker 认领任务、执行失败、等待 5 秒、重试三次都失败后任务标记为 failed。这个过程主节点完全不受影响Web UI 依然流畅。这就是队列模式的意义失败任务在 Worker 侧消化不会阻塞调度。第三类节点宕机。这是高可用的关键验证。先扩容 Worker 到 3 个docker compose up -d --scale n8n-worker3然后手动杀掉一个 Workerdocker compose ps # 找到某个 n8n-worker 的容器 ID docker kill 容器ID观察另外两个 Worker 的日志它们会继续从 Redis 认领任务队列不会因为一个节点挂掉而停摆。被 kill 的 Worker 如果配置了restart: alwaysDocker 会自动拉起它重新接入队列。这个过程里正在执行的任务会怎样BullMQ 有任务锁机制Worker 挂掉后锁超时任务会被重新投递到队列由其他 Worker 接手。这就是「至少执行一次」的语义所以你的工作流要尽量做成幂等的。验证完这三类集群基本可以上生产了。如果工作流里有 AI 代码执行再单独看 Runner 日志docker compose logs -f n8n-runner沙盒执行成功会打印任务 ID 和耗时失败会打印堆栈方便定位是代码问题还是鉴权问题。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth部署过程中我踩过的坑集中在四类报错逐个说。401 Unauthorized。出现在两个地方。一是 n8n 凭证里 TaoToken 的 Key 填错或过期重新在控制台生成一个注意复制时别带空格。二是 Runner 沙盒鉴权失败报错通常是N8N_RUNNERS_AUTH_TOKEN mismatch。检查主节点和 Runner 的RUNNERS_AUTH_TOKEN是否完全一致这个值在.env里定义所有服务通过变量引用理论上不会不一致除非你手动改了某个服务的 environment。改完记得docker compose up -d重建容器光 restart 不会重新读.env。local proxy failed。这个报错说明容器内的代理配置有问题。常见原因是NO_PROXY白名单没包含集群内部服务名导致 Worker 访问 Redis 时走了代理。检查NO_PROXY里是否有redis、postgres、n8n、n8n-worker、n8n-runner。另一个原因是代理地址写成了localhost但容器里的 localhost 是容器自己不是宿主机。代理地址要写宿主机的内网 IP比如http://192.168.100.103:7890。reading choices 报错。这是 OpenAI 节点解析响应时找不到choices字段。原因通常是 Base URL 配错了比如写成了https://taotoken.net而不是https://taotoken.net/api请求打到了网页而不是接口返回的是 HTML。或者 Model ID 填了一个不存在的模型接口返回错误结构。检查 Base URL 是否带/apiModel ID 是否在控制台模型列表里。用 HTTP Request 节点手动调一次https://taotoken.net/api/v1/chat/completions看返回结构对不对。OAuth 相关报错。如果你用 n8n 接 Google、GitHub 这类 OAuth 服务回调地址必须和WEBHOOK_URL一致。分布式部署下WEBHOOK_URL要填对外网关地址不能填容器内部地址。比如你前面挂了 NginxWEBHOOK_URL就填https://n8n.yourdomain.com/末尾斜杠别丢。OAuth 回调走的是这个地址填错会报redirect_uri_mismatch。还有一个隐蔽的坑EXECUTIONS_DATA_SAVE_ON_SUCCESSnone会让成功任务的详细日志不落库如果你在调试阶段想看每次执行的输入输出先临时改成all调完再改回来。生产环境保持none否则高频任务会把 Postgres 磁盘写满。排查顺序建议先看docker compose ps确认服务状态再看对应服务日志最后用 curl 直接打接口验证。大部分问题出在环境变量和网络配置代码层面的问题反而少。6. 长期跑 Agent 工作流把 Coding Plan 用起来集群搭好之后真正的挑战才刚开始工作流会越来越多AI Agent 类任务会越来越重模型调用量会持续上涨。这时候按量付费的成本会变得不可控尤其是那些高频轮询、批量处理的工作流。如果你的团队长期在 n8n 里跑编码类、Agent 类工作流可以看看 TaoToken 的 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。它适合需要稳定调用额度、不想每次调用都单独计费的场景。配合前面配好的统一网关Worker 扩容到多少个都不影响模型出口的稳定性。接入文档在这里里面有各语言 SDK 和 HTTP 调用的完整示例https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。n8n 里用 HTTP Request 节点对接的话重点看鉴权头和请求体格式那两节。最后说个实操细节Worker 扩容不是越多越好。每个 Worker 都会占用内存和数据库连接Postgres 的max_connections默认 100Worker 数量乘以每个 Worker 的连接池大小不能超过这个数。我一般按「单 Worker 处理 5-10 个并发任务」估算先 scale 到 3 个观察队列积压情况再决定要不要加。缩容用docker compose up -d --scale n8n-worker1高峰期过了就收回来别让空闲 Worker 白占资源。
阅读完成 · 觉得有帮助?