gunicorn无法真正热重载django,因其worker进程需重启才能加载新代码;django启动时执行的settings加载、信号注册、缓存初始化等有状态操作仅在fork后运行一次,sighup仅触发冷重启而非内存级热更新。

不能直接热重载 Django 应用本身 —— Gunicorn 的 worker 进程必须重启才能加载新代码,但可以做到“零停机”切换。
为什么 gunicorn 无法真正热重载 Django
Django 是有状态的:启动时加载 settings、注册信号、初始化缓存后端、编译模板等。这些操作只在 worker 进程 fork 后执行一次。即使你发 SIGHUP 给 Gunicorn 主进程,它只会 reload worker 进程(即 kill + fork),但新 worker 仍会重新 import 全部模块 —— 这不是“热更新”,而是“冷重启”。如果你没做平滑过渡,用户可能收到 502 Bad Gateway 或短暂超时。
- 现象:
kill -SIGHUP $(cat gunicorn.pid)后,旧请求还在处理,新请求却失败 - 根本原因:Nginx 默认不等待旧连接关闭,且 Gunicorn 新 worker 尚未 ready 就开始接收请求
- 关键点:
--preload选项会让 Gunicorn 在 fork 前就 import 所有模块,反而加剧冷启动延迟,生产环境应禁用
用 gunicorn --preload=False + systemd 滚动重启
这是最可控、无需额外工具链的方案。核心是让 systemd 管理 Gunicorn 实例,并利用其 Type=notify 和 Restart=on-failure 能力,配合 Nginx 的健康检查实现无感切换。
- 确保
gunicorn.conf.py中显式关闭 preload:preload = False - systemd service 文件中启用
NotifyAccess=all,并在 Gunicorn 启动参数加--enable-stdio-inheritance(便于日志捕获) - 升级流程:
- 推送新代码并安装依赖:
pip install -r requirements.txt --force-reinstall - 运行迁移(如需):
python manage.py migrate --noinput - 触发 systemd 重启:
systemctl reload-or-restart gunicorn.service
- 推送新代码并安装依赖:
- systemd 会先启动新实例,等其通过
READY=1通知后,再优雅停止旧实例 —— 前提是你的 Gunicorn 配置里有preload = False且应用启动快(
Nginx 配合 upstream 的健康探测与 graceful shutdown
仅靠 systemd 不够:如果新 Gunicorn 启动失败或卡在某个初始化步骤(比如连不上 Redis),Nginx 仍会把流量切过去。必须让 Nginx 主动感知后端是否真正可用。
- 在
upstream块中启用健康检查:health_check interval=5 fails=3 passes=2; - 为 Gunicorn 添加一个轻量级健康端点(例如
/health/),只检查 DB 连接和缓存可写,不查外部服务 - 确保该端点返回
200且响应时间 - 避免用
max_fails+fail_timeout这种被动机制,它会导致故障扩散;主动health_check更可靠
升级时最容易被忽略的三个点
很多团队卡在看似“已重启成功”的假象里:
-
manage.py collectstatic必须在重启前完成,否则新 worker 启动时可能因静态文件缺失报错(尤其用了ManifestStaticFilesStorage) - 缓存键格式变更(比如从
django.core.cache.backends.redis.RedisCache升级到 5.x 后默认使用新序列化协议)会导致旧 key 无法读取,建议升级前后清空 Redis 中的缓存 - 数据库连接池(如
pgbouncer或mysql-connector-python的 pool_size)若配置过小,在新旧 worker 切换瞬间并发激增,可能触发连接数超限 —— 要预留至少 2× 当前峰值连接数
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











