Django + Gunicorn的 API间歇性挂起(504错误)——工作进程看起来处于空闲状态,但请求从未完成

后端开发 2026-07-09

我在Docker容器中运行一个Django应用,使用Gunicorn作为服务器,并配置多工作进程和多线程。系统还包含PostgreSQL、Redis,以及多个Celery任务进程。

问题在服务持续运行大约4 天后出现。

问题

在大约4 天的正常运行后:

  • 所有前端API请求开始失败,错误为 504 Gateway Timeout
  • Django管理后台仍然正常工作
  • Gunicorn的工作进程仍在运行(没有崩溃)
  • 未发生容器重启
  • CPU使用率较低,工作进程看起来处于空闲状态(ep_polldo_select
  • 请求从未完成(没有返回响应)

观察

  • 数据库连接保持健康:

  • 空闲:5–7

  • 活跃:1

  • Gunicorn进程仍在运行:

  • 未观察到工作进程崩溃

  • 未出现重启循环

  • 内部健康检查日志显示:

  • 响应码:000(未接收到HTTP响应)

Gunicorn配置

gunicorn alloy_admin.wsgi:application
--workers 3
--threads 2
--timeout 120
--max-requests 1000
--max-requests-jitter 100
--keep-alive 5

提问

是什么原因会让一个Django + Gunicorn的组合在正常工作好几天后,逐渐进入所有API请求卡住、最终返回504的状态,同时工作进程仍然存活且处于空闲?

这通常是由以下原因导致的吗:

  • 线程耗尽 / 工作进程饱和?
  • 视图内阻塞的同步外部API调用?
  • 连接池耗尽(数据库 / HTTP)?
  • Nginx / 代理超时问题?
  • 还是Gunicorn工作进程中长期存在的内存/状态问题?

对于这种“延迟性全局挂起”且系统随时间下降且不崩溃的情况,推荐的排查方法是什么?

解决方案

在代理层积压请求、工作进程仍然存活且CPU低时,通常意味着以下两种情况之一:

  • 请求不再可靠地到达Gunicorn的工作进程(在代理和Gunicorn之间的某个地方出现backlog/文件描述符/套接字耗尽)
  • 工作进程被阻塞在外部I/O上(HTTP调用、Redis、DNS、上游服务等)

健康的数据库连接池,以及处于 epollselect 的工作进程,使数据库瓶颈或CPU饱和的可能性降低。

管理后台仍在工作并不一定排除问题。后台管理的流量通常要小得多,可能绕开了你的API端点所涉及的代码路径或依赖。

再次出现问题时,我会检查工作进程到底在等待什么:

strace -fp <worker-pid>

如果工作进程一直处于 epoll_wait,没有任何活动,说明请求可能根本没有到达它们。
如果它们被阻塞在 recvsendconnect、DNS等系统调用中,那么工作进程很可能在等待某个外部依赖。

同时检查代理与Gunicorn之间的套接字状态:

ss -tanp | grep gunicorn
lsof -p <gunicorn-pid>

注意大量的:

  • SYN_RECV
  • CLOSE_WAIT
  • 长时建立的连接
  • 异常高的文件描述符数量

另一个有用的信号是:系统卡住时,请求是否仍然出现在Gunicorn的访问日志中?如果nginx/代理记录了请求但Gunicorn从未看到它们,这更指向工作进程前的连接排队/资源耗尽。

如果视图/任务中有同步的外部调用,也应检查。少量被卡住的外部请求最终可能耗尽所有可用的工作进程/线程,使整个API看起来不可用,而进程本身仍然健康。

在卡住时,我会从 strace 开始排查。这通常可以很快把问题定位到位。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章