拆解 Dify 的 Celery:15 个队列都在干什么
Dify 的任务提交后一直「等待中」、知识库索引转几小时还是排队、工作流执行完了状态没更新------这些问题的根因,都藏在那 15 个 Celery 队列里。
📌 适用版本:Dify v1.9.x --- e-1.15.0 | 最后更新:2026-07-04
⚠️ Dify 版本迭代频繁,部分参数默认值可能随新版变化。本文发布时已对照官方最新文档校准,若发现配置项与你当前版本不一致,请以
docker/.env中的实际值为准,并查阅 官方环境变量文档。
核心结论
Dify 默认只跑 1 个 Celery Worker(4 并发进程),处理 15 个独立队列。 没有优先级机制------workflow 队列堆 500 个任务时,Worker 可能还在处理 dataset 队列里的慢速文档索引。
三个最容易被忽略的坑:
TENANT_ISOLATED_TASK_CONCURRENCY=1→ 同一 workspace 下知识库索引全部串行APP_MAX_EXECUTION_TIME和WORKFLOW_MAX_EXECUTION_TIME不联动 → 长工作流被单边切断CELERY_AUTO_SCALE默认关闭 → Worker 数量固定,不会自动应对突发
🧭 本文是「Dify 并发急救手册」系列第 3 篇。前两篇讲了「6 层瓶颈诊断法」和「Gunicorn + Nginx 配置」------本文深入第 3 层(Celery 异步层),拆解 15 个队列的拓扑和隐藏瓶颈。
先看整体:为什么需要 15 个队列
Dify 的任务远不止「调用一次 LLM」这么简单。它包含了文档切块、向量嵌入、知识库索引、邮件发送、工作流调度、代码执行、数据导出等等。这些任务的耗时从毫秒级到小时级不等。如果全塞进一个队列,一个慢任务堵住,后面所有任务都排着。
所以 Dify 按任务类型分了 15 个独立队列,每个队列走独立的消费者。
markdown
┌─────────────────┐
│ Celery App │
└────────┬────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │ │ │
workflow generation dataset mail ops
│ │ │ │ │
执行工作流 LLM 调用 知识库索引 邮件发送 运维任务
5 个核心队列,一个一个拆
workflow --- 工作流执行
流量最大、也最容易堵的队列。
bash
# 看积压情况
docker exec -it dify-redis-1 redis-cli LLEN workflow
# > 100 → 工作流排队严重
每条工作流的每一步执行------节点间的调度、变量传递、条件分支判断------都走这个队列。如果你并行节点多、工作流步骤长,这个队列很快就能从 0 涨到几百。
排查思路: 如果 workflow 队列积压,先看 GraphEngine 是否在并行执行(见系列第 4 篇)、再看 Worker 够不够。
generation --- LLM 推理调用
所有调用外部模型的请求------Chat、Completion、Embedding------都走这里。这个队列的特点是:单个任务耗时长(几秒到几十秒),但不怎么占 CPU。
bash
docker exec -it dify-redis-1 redis-cli LLEN generation
# 持续增长 → Worker 消费速度跟不上请求速度
如果这个队列积压而 workflow 队列正常,说明不是工作流本身卡住了,是模型响应慢或者 Key 的 RPM 触顶了(见系列第 5 篇)。
dataset --- 知识库操作
文档上传后的全文索引、向量化、段落切分全走这里。这个队列的问题是:一个文档可能要几分钟甚至几十分钟,且不能并发太多(影响数据库写入) 。
bash
docker exec -it dify-redis-1 redis-cli LLEN dataset
# > 100 → 知识库索引排队严重
mail --- 邮件发送
这个队列一般不会堵,但它暴露了一个经常被忽略的配置坑:Dify 邮件服务的配置散落在多个地方,一套没配好,邮件发不出去你还不知道------因为 Celery 默默地把它吞了。
ops --- 运维任务
系统维护、定时清理、数据迁移这些小任务。优先级低,一般不关注。
真正的问题:不是队列太多,是 Worker 太笨
Dify 默认只跑 1 个 Celery Worker 容器,4 个并发进程。这 4 个进程从 15 个队列里抢任务------问题是它们没有优先级概念:workflow 队列堆 500 个任务等着救命,Worker 可能还在慢悠悠地处理 dataset 队列里一个 20 分钟的文档索引。
perl
# 看看你现在有几个 Worker
docker compose ps | grep worker
# 大概率只有 1 个
方案一:扩容通用 Worker(最简单)
ini
# 直接扩到 3 个 Worker 实例
docker compose up -d --scale worker=3
每个实例 4 个进程,3 × 4 = 12 并发消费能力。实测 4 核 8GB 机器上,12 并发可稳定处理 50-80 QPS 的混合任务负载。
方案二:按队列拆分专用 Worker(高负载场景)
当你的 workload 和 generation 队列长期积压,可以给它们专门的 Worker。
yaml
# docker-compose.yml 里加两个专用 Worker
worker-workflow:
image: langgenius/dify-api:latest
command: celery -A app.celery worker -Q workflow,generation -c 8
# ↑ 只消费 workflow 和 generation,8 个并发
worker-dataset:
image: langgenius/dify-api:latest
command: celery -A app.celery worker -Q dataset,mail,ops -c 2
# ↑ 慢任务用少量并发,避免影响快速队列
这样 workflow 队列再堵也不会影响 worker-dataset 正常处理知识库。
隐藏杀手:TENANT_ISOLATED_TASK_CONCURRENCY
前面说的都是全局层面的队列。下面这个参数是租户级的隐性限制,全网几乎没人提过:
bash
grep TENANT_ISOLATED_TASK_CONCURRENCY docker/.env
# 输出 1(或没找到这一行 = 默认 1)
它的含义是:每个租户(workspace)同时只能跑 1 个知识库索引任务。
也就是说------就算你扩了 10 个 Celery Worker、dataset 队列是空的------如果你的 workspace 下有 3 个知识库同时触发索引,它们还是一个一个排队,不会并行。
ini
# 多知识库场景必须调这个
TENANT_ISOLATED_TASK_CONCURRENCY=3
验证方法: 同时触发 3 个知识库的全量索引,看 Dify 控制台的索引进度------如果它们是交替跑而非同时跑,就是这个参数卡住了。
CELERY_AUTO_SCALE:别关着不用
默认 CELERY_AUTO_SCALE=false,Worker 数量是固定的。
ini
CELERY_AUTO_SCALE=true
CELERY_MIN_WORKERS=2
CELERY_MAX_WORKERS=8
启用后,Celery 会根据队列深度自动扩缩------队列空的时候只跑 2 个进程省资源,队列堆积时自动开到 8 个。
⚠️ 注意:
CELERY_MAX_WORKERS的值不能超过你的 CPU 核数。8 核机器最多设 8。
双超时陷阱:为什么调了也没用
文章开头提到一个经典症状:「工作流执行完成了,但状态没更新」。
这个问题的根因不是 Celery Worker 挂了,而是两个超时参数你没对齐。
Dify 有两个层面的超时控制:
| 参数 | 作用层 | 默认值 |
|---|---|---|
APP_MAX_EXECUTION_TIME |
队列层(Celery) | 1200s |
WORKFLOW_MAX_EXECUTION_TIME |
引擎层(GraphEngine) | 1200s |
这两个参数分布在不同的配置章节,文档也只说"各管各的"。实际效果是:只要有一个超时了,工作流就断。
举个常见的坑:你的工作流实际跑 25 分钟(1500s)。你把 WORKFLOW_MAX_EXECUTION_TIME 调到了 1800s,觉得够了吧?但 APP_MAX_EXECUTION_TIME 还是 1200s。结果引擎层还在跑,队列层已经超时把任务标记为失败了------用户看到的就是"执行完成了但状态显示超时"。
ini
# 修复:两个一起调
APP_MAX_EXECUTION_TIME=1800
WORKFLOW_MAX_EXECUTION_TIME=1800
这条规则也适用于长工作流场景------改一个不改另一个,等于没改。
快速自查
bash
# 1. 哪个队列在堵?
for q in workflow generation dataset mail ops; do
echo "$q: $(docker exec -it dify-redis-1 redis-cli LLEN $q 2>/dev/null)"
done
# 2. Worker 够用吗?
docker compose ps | grep worker | wc -l
# 1 个 → 太少了
# 3. Worker 在忙什么?
docker exec -it dify-worker-1 celery -A app.celery inspect active 2>/dev/null
# 4. 租户级限制在卡吗?
grep TENANT_ISOLATED_TASK_CONCURRENCY docker/.env
# 1 → 多知识库场景必须调
# 5. 自动扩缩开了吗?
grep CELERY_AUTO_SCALE docker/.env
# false → 建议开
# 6. 双超时对齐了吗?
grep -E "APP_MAX_EXECUTION_TIME|WORKFLOW_MAX_EXECUTION_TIME" docker/.env
# 两个值必须一致,否则长工作流被单边切断
接下来
本文是「Dify 并发急救手册」系列第 3 篇。下一篇------你的 Dify 并行节点其实在串行跑:v1.9.0 的 GraphEngine Bug 全揭秘。
👉 关注「辉的技术笔记」,知乎/掘金同步更新,每篇背后都是一段真实踩坑经历。
标签: Dify | 后端 | 运维 | Celery | Docker