Django+Gunicorn运行多日后API间歇性挂起(504)问题咨询
问题场景
Docker部署的Django应用,采用多进程多线程Gunicorn(3个Worker,每个Worker配2线程),搭配PostgreSQL、Redis及多个Celery Worker。服务连续运行约4天后出现异常:前端API请求全部返回504 Gateway Timeout,但Django admin面板正常可用;Gunicorn Worker无崩溃、容器未重启,CPU使用率低,Worker处于ep_poll/do_select空闲状态,请求始终无法完成。
已观测结果
- 数据库连接健康:空闲连接5-7个,活跃连接仅1个
- Gunicorn进程状态:未发现Worker崩溃或重启循环
- 内部健康检查日志:响应码为000(未收到HTTP响应)
Gunicorn配置
gunicorn alloy_admin.wsgi:application --workers 3 --threads 2 --timeout 120 --max-requests 1000 --max-requests-jitter 100 --keep-alive 5
可能原因分析
针对列出的几种可能性逐一排查:
1. 线程耗尽/Worker饱和?
从Worker处于ep_poll/do_select空闲状态来看,当前没有请求占用线程或Worker,直接排除该可能。若线程耗尽,Worker应处于忙碌状态(比如卡在某个调用上),而非空闲等待。
2. 视图中存在阻塞同步外部API调用?
若API视图存在未设置超时的阻塞外部调用,可能导致线程被长时间占用,但当前Worker处于空闲状态,说明所有线程未被这类调用阻塞。不过仍需检查:是否有API视图未处理外部调用的超时逻辑,是否曾有请求触发无超时阻塞导致线程永久挂起(但当前Worker空闲,该可能性极低)。
3. 连接池耗尽(数据库/HTTP)?
数据库连接显示健康(空闲多、活跃少),排除数据库连接池耗尽。但需检查HTTP连接池(如requests库的全局连接池):若应用使用全局连接池且未正确关闭连接,长期运行后可能导致连接池占满,新请求无法获取连接发起外部调用,进而挂起?不过当前Worker空闲,该情况可能性较低,仍需排查确认。
4. Nginx/代理超时问题?
前端返回504 Gateway Timeout是代理层(如Nginx)的超时提示,但内部健康检查返回000(未收到HTTP响应),说明请求已到达Gunicorn,但Gunicorn未返回任何响应。若为代理超时,代理应是等待超时但Gunicorn已处理完成,而当前情况是请求在Gunicorn端卡住,因此不是代理本身的超时配置问题。
5. Gunicorn Worker的长期内存/状态问题?
Gunicorn配置了--max-requests 1000,会自动重启Worker(jitter=100,即处理900-1100个请求后重启),但仍可能出现状态损坏:
- 全局变量泄漏或被错误修改,导致后续请求无法进入处理流程
- 文件描述符泄漏:长期运行后Worker打开的文件描述符达到系统上限,无法接受新请求,表现为空闲状态(因无法读取新请求数据)
- 异步相关库导致的事件循环阻塞(虽Gunicorn为同步模式,但代码中若引入异步组件可能出现异常)
该情况是高概率原因,符合服务运行4天后随时间退化的特征。
推荐调试方法
针对这类无崩溃但随时间退化的挂起问题,按以下步骤排查:
实时监控Worker状态
- 用
ps aux查看Gunicorn Worker的进程状态,确认是否有进程处于D(不可中断睡眠)状态 - 用
lsof -p <worker-pid>查看Worker打开的文件描述符数量,是否接近系统上限(可通过ulimit -n查看)
- 用
捕获请求处理栈
- 问题出现时,用
py-spy dump -p <worker-pid>获取Worker的Python调用栈,查看是否有请求卡在某个环节 - 或用
gdb附加到Worker进程,查看系统调用栈
- 问题出现时,用
增强日志输出
- 在Django的
settings.py中开启详细日志,记录每个请求的开始、结束时间及视图关键步骤 - 给Gunicorn添加
--log-level debug,查看Worker是否接收到请求及请求处理的阶段
- 在Django的
模拟压测复现
- 用
locust或ab工具对API进行压测,模拟高并发场景,尝试复现问题 - 重点测试涉及外部调用、数据库操作的API视图
- 用
排查资源泄漏
- 用
memory_profiler监控Worker的内存变化,确认是否存在内存泄漏 - 检查代码中是否有未关闭的文件、数据库连接、HTTP连接,尤其是全局对象中的资源
- 用
调整Gunicorn配置验证
- 降低
--max-requests值(如设为500),让Worker更频繁重启,观察是否能延迟或避免问题 - 增加Worker数量或线程数(当前Worker空闲,该步骤优先级低)
- 降低
内容的提问来源于stack exchange,提问作者Munir Ahmad

