引言
最近在使用 Dify 工作流中的 HTTP 请求节点调用一个第三方 API 时,遇到了一个令人困惑的超时问题。接口响应本身较慢(约 2 分钟),但直接通过接口文档工具(如 Postman)调用却完全正常。然而一旦放到 Dify 的 HTTP 节点中,请求就在 125 秒左右报超时。
本文将完整记录整个排查过程,从现象到根因,希望能帮助遇到类似问题的同学快速定位。
第一步:确认现象
测试数据
| 测试场景 | 超时时间 |
|---|---|
| 开启重试(默认 3 次) | 约 480 秒 |
| 关闭重试 | 124.696 秒 ≈ 125 秒 |
| 直接调用 API(脱离 Dify) | 正常返回 |
初步分析
- 480 秒 ≈ 4 × 120 秒,刚好是 3 次重试 + 1 次初始请求
- 125 秒 = 120 秒 + 几秒的额外开销
- 直接调用正常,说明 目标服务器本身没问题
这个 120 秒的整数倍特征非常可疑,我们开始顺藤摸瓜。
第二步:排查 Dify 应用层超时
首先检查 Dify 的 HTTP 请求节点配置参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
HTTP_REQUEST_MAX_CONNECT_TIMEOUT |
10s | 连接超时 |
HTTP_REQUEST_MAX_READ_TIMEOUT |
600s | 读取超时 |
HTTP_REQUEST_MAX_WRITE_TIMEOUT |
600s | 写入超时 |
SSRF_DEFAULT_MAX_RETRIES |
3 | 最大重试次数 |
600 秒的默认超时远大于 125 秒,Dify 应用层不是瓶颈。而且 125 秒也不是一个常见的应用层超时配置值。
排查其他超时参数
| 参数 | 值 | 结论 |
|---|---|---|
GUNICORN_TIMEOUT |
360s | ❌ 不匹配 |
WORKFLOW_MAX_EXECUTION_TIME |
1200s | ❌ 不匹配 |
NGINX_PROXY_READ_TIMEOUT |
3600s | ❌ 不匹配 |
都不是。
第三步:追踪请求链路
Dify 的 HTTP 请求节点经过了完整的请求链路:
arduino
HTTP 节点 → graphon 执行器 → httpx.Client → Squid SSRF 代理 → 目标服务器
关键点:Dify 出于安全考虑(防 SSRF 攻击),所有 HTTP 请求都通过 Squid 代理 转发。
第四步:锁定真凶 --- Squid 代理超时
查看 Squid 的配置文件 docker/ssrf_proxy/squid.conf.template:
nginx
# Timeout configurations for image requests
connect_timeout 30 seconds
request_timeout 2 minutes # ← 120 秒
read_timeout 2 minutes # ← 120 秒
client_lifetime 5 minutes
找到问题了! Squid 的 read_timeout 和 request_timeout 都是 2 分钟(120 秒)。
两个超时的区别
request_timeout:等待客户端发送完整请求的时间 --- 不相关read_timeout:向目标服务器发起请求后,等待响应数据的时间 --- ✅ 这就是真凶
当目标 API 处理时间超过 2 分钟时,Squid 主动断开连接,Dify 收到连接断开异常,最终报出 124.696 秒 的超时(120 秒 Squid 超时 + 几秒的异常传播开销)。
这也完美解释了 480 秒的情况:
开启重试(3 次):
第 1 次: 125s → 超时 → 重试
第 2 次: 125s → 超时 → 重试
第 3 次: 125s → 超时 → 重试
第 4 次: 125s → 超时 → 报错
总计: ~500s ✅
第五步:修复方案
修改 Squid 配置
bash
# 在 docker/ssrf_proxy/squid.conf.template 中
# 将 2 minutes 改为 30 minutes
request_timeout 30 minutes
read_timeout 30 minutes
无需重新打包镜像
Squid 配置是通过 Docker volume 挂载进容器的,所以 不需要重新构建镜像,只需重启容器:
bash
docker compose restart ssrf_proxy
重启后容器会自动从模板重新生成配置文件并加载。
经验总结
1. 理解 Dify 的请求链路
Dify 的 HTTP 请求节点并非直连目标服务器,而是经过:
java
Dify API → SSRF Proxy (Squid) → 目标服务器
SSRF 代理是安全设计,但会引入额外的超时层。
2. 超时对比表
| 层级 | 参数 | 默认值 | 说明 |
|---|---|---|---|
| Dify 应用层 | HTTP_REQUEST_MAX_READ_TIMEOUT |
600s | 可通过环境变量或 UI 配置 |
| Squid 代理 | read_timeout |
120s ⚠️ | 硬编码在配置模板中 |
| Gunicorn | GUNICORN_TIMEOUT |
360s | WSGI worker 超时 |
| Nginx | NGINX_PROXY_READ_TIMEOUT |
3600s | 反向代理超时 |
| 工作流 | WORKFLOW_MAX_EXECUTION_TIME |
1200s | 整个工作流执行上限 |
任何一层超时都会导致请求失败,且最严格的超时最先触发。
3. 排查方法论
当遇到超时问题时,可以按以下思路排查:
- 收集数据:记录不同场景下的超时时间(开/关重试、直接调用等)
- 找公约数:125 秒、480 秒 --- 120 秒的整数倍是明显的信号
- 追踪链路:了解请求经过的所有中间件
- 逐层比对:将每个中间件的超时配置与观测值对比
- 验证假设:修改配置,重启服务,测试验证
4. 教训
- 不要忽略基础设施层的超时配置(代理、网关、负载均衡)
- 应用层配置的超时(如
read_timeout=600s)并不代表实际生效的值 - 最小的超时值决定了整个链路的超时上限
- 125 秒这个"不常见"的数字,往往是 120 秒(2 分钟)加上异常传播的额外开销
结语
这次排查从 Dify 应用层一路追到 Squid 代理层,最终发现是 2 分钟的 Squid read_timeout 导致的。问题的根源并不复杂,但涉及多个中间件层级,需要系统性地逐层排查。
如果你也在 Dify 中遇到类似的问题,不妨先检查一下 Squid 代理的超时配置,它可能是那个"沉默的瓶颈"。