把Python Web应用部署到生产环境,Django和Flask的默认启动方式绝对不能直接用。python manage.py runserver或者flask run这种单进程、单线程的开发服务器,并发能力几乎为零,随便几个请求就能让它阻塞。生产环境真正要解决的是进程管理、资源利用率和安全更新的问题,这两个框架在具体操作上有明显差异,但核心思路相通。
Django的生产进程管理:Gunicorn + Uvicorn的搭配逻辑Django是同步框架,原生的WSGI接口一次只能处理一个请求。直接在生产环境用Gunicorn的默认sync worker,每个worker进程同时只能服务一个客户端。如果你的视图里有数据库查询或外部API调用,这个worker就会一直卡在那等响应,其他请求只能排队。并发量的计算公式很简单:并发数 = worker数量。假设你起了4个worker,第5个请求进来就得等前面的处理完。
解决方式是换用异步worker或者协程worker。Gunicorn从20.0版本开始支持Uvicorn的worker类,能直接把Django跑在ASGI协议上。具体做法是先装uvicorn和gunicorn,然后启动命令改成:
gunicorn myproject.wsgi:application \
--bind 0.0.0.0:8000 \
--worker-class uvicorn.workers.UvicornWorker \
--workers 4 \
--threads 2 \
--max-requests 10000 \
--max-requests-jitter 1000 \
--timeout 120 \
--keep-alive 5
这里有几个参数必须理解清楚。--worker-class指定用UvicornWorker,每个worker内部是asyncio事件循环,能同时处理多个并发连接。--workers设置进程数,一般按CPU核心数的2到4倍来配,但不要超过数据库连接池的上限。--threads是给sync worker用的,如果你用UvicornWorker这个参数会被忽略,因为异步worker不依赖多线程。--max-requests和--max-requests-jitter配合使用,让每个worker处理完一定数量的请求后自动重启,防止内存泄漏累积。jitter参数加一个随机偏移量,避免所有worker同时重启导致服务抖动。
Django 3.1之后原生支持异步视图,如果你用了async def写视图函数,必须用ASGI服务器跑,WSGI模式下异步视图会被转成同步执行,性能优势全没了。此时Gunicorn的配置里worker-class要换成uvicorn.workers.UvicornWorker,或者直接用Uvicorn启动:
uvicorn myproject.asgi:application \
--host 0.0.0.0 \
--port 8000 \
--workers 4 \
--limit-concurrency 1000 \
--backlog 2048
Uvicorn本身也支持多worker模式,但它的进程管理不如Gunicorn成熟。更稳妥的方案是Gunicorn做进程管理器,Uvicorn做worker执行引擎,各取所长。
Flask的进程管理:同步框架的并发困境与出路Flask同样是同步WSGI框架,但它的生态更灵活,部署方案也更多样。最基础的部署是用Gunicorn的sync worker,但和Django一样会遇到并发瓶颈。Flask没有Django那么重的ORM和中间件,单个请求的处理时间通常更短,所以sync worker在低并发场景下也能撑住,但一旦涉及长连接、文件上传、SSE推送,问题立刻暴露。
对于Flask,Gunicorn的配置建议用gevent worker:
gunicorn app:app \
--bind 0.0.0.0:5000 \
--worker-class gevent \
--workers 4 \
--worker-connections 1000 \
--max-requests 50000 \
--max-requests-jitter 5000 \
--graceful-timeout 30
gevent通过monkey patch把标准库里的阻塞IO操作替换成协程版本,一个worker能同时处理上千个连接。--worker-connections参数控制每个worker的最大并发连接数,这个值设多大取决于你的内存和文件描述符上限。注意gevent和某些C扩展库不兼容,比如某些数据库驱动,如果你用了psycopg2,要换成psycopg2-binary或者用pgbouncer做连接池。
另一个选择是用异步网关做前置。比如把Flask跑在Gunicorn后面,前面加一层Nginx做反向代理和缓冲。Nginx能扛住慢客户端,把完整的请求体收完再转发给后端,避免后端worker被慢速连接拖住。这个架构对Django同样适用,但对Flask这种轻量框架收益更大,因为Flask本身没有内置的请求缓冲机制。
进程守护与自动重启:systemd和Supervisor的取舍生产环境进程不能手动启动,挂了也不能等人来重启。systemd是现代Linux发行版的标准方案,配置一个服务文件/etc/systemd/system/myapp.service:
[Unit]
Description=My Django/Flask Application
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/venv/bin/gunicorn myproject.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker
Restart=always
RestartSec=5
EnvironmentFile=/opt/myapp/.env
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Restart=always确保进程无论什么原因退出都会被拉起来,RestartSec=5给5秒的冷却时间防止疯狂重启。StandardOutput和StandardError指向journal,日志统一用journalctl查看。EnvironmentFile用来加载环境变量,敏感信息别写在配置里。
Supervisor在老项目里还很常见,配置更直观但功能不如systemd强大。如果你用Docker部署,容器本身就是进程守护边界,Docker daemon配合--restart=always策略能达到类似效果。但容器内部还是建议跑一个init进程比如tini,来回收僵尸进程和转发信号。
零停机更新:信号驱动的平滑重启代码更新后不能直接kill进程然后重启,这会导致正在处理的请求被强制中断。Gunicorn支持通过发送HUP信号来实现平滑重启:
kill -HUP $(cat /var/run/gunicorn.pid)
收到HUP信号后,Gunicorn会先启动一批新的worker,等新worker准备好接受请求后,再优雅关闭旧worker。旧worker会等当前请求处理完再退出,graceful-timeout参数控制最长等待时间。这个过程不会丢失任何请求,前提是你的Nginx或负载均衡器配置了健康检查和重试。
更高级的做法是用USR2信号配合新老进程交替,这在需要切换Gunicorn版本或Python版本时特别有用。流程是:发送USR2信号启动一套新的master进程,新master用新的二进制和代码;旧master继续运行直到你发送QUIT信号。这个过程中新旧两套进程同时监听同一个端口(通过SO_REUSEPORT),请求不会中断。
Django的更新还有个特殊问题:代码变更后可能需要执行数据库迁移。迁移操作通常不能在线做,会锁表或修改表结构导致查询失败。策略是先用迁移生成新表或新字段,保持旧字段不变,等所有实例都更新代码后再清理旧字段。这个过程叫向后兼容迁移,需要分多次部署完成。
安全更新的最佳实践:依赖锁定与漏洞扫描pip freeze输出的依赖列表不够精确,子依赖的版本没有锁定。应该用pip-tools或者Poetry来管理依赖,生成精确的lock文件。requirements.txt里每个包的版本钉死到补丁版本号:
Django==4.2.11 gunicorn==22.0.0 uvicorn==0.29.0 psycopg2-binary==2.9.9
定期用pip-audit扫描已知漏洞:
pip-audit -r requirements.txt
这个命令会对照PyPA的漏洞数据库检查每个包是否存在已知CVE。CI/CD流水线里应该加入这个检查步骤,发现高危漏洞直接阻断部署。
Django的安全更新尤其重要,因为它的攻击面大。Django的安全团队会在发布新版本前私下通知大型用户,普通用户应该订阅django-announce邮件列表。安全补丁发布后,评估影响范围,如果漏洞可被远程利用且无需认证,必须立即更新。更新步骤是:先在staging环境部署验证,然后对生产环境逐个实例滚动更新,确保至少有一个实例始终在线。
Flask的依赖更轻量,但它的扩展生态碎片化严重。一个Flask项目可能装了十几个扩展,每个扩展的维护质量参差不齐。安全更新的重点除了Flask本身,还要关注Flask-Login、Flask-SQLAlchemy、Flask-WTF这些核心扩展。建议用pip list --outdated定期检查过期包,但升级前一定要看changelog,Flask扩展的大版本经常有破坏性变更。
容器化部署中的进程管理差异Docker环境下,容器内跑多进程是反模式,但Gunicorn的多worker算例外,因为它们共享同一个master进程管理。容器里应该用exec形式启动Gunicorn,让Gunicorn的master作为PID 1:
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
Kubernetes环境下,进程管理由Pod的生命周期接管。Gunicorn的worker数量可以适当减少,因为水平扩展靠增加Pod副本数,而不是单Pod内堆worker。每个Pod配2个worker,通过HPA设置CPU使用率阈值自动扩缩容,比固定4个worker更灵活。健康检查用就绪探针探测/health端点,存活探针直接检查进程是否存在。
安全更新在Kubernetes里变成了滚动更新策略。修改Deployment的镜像tag,Kubernetes会按RollingUpdate策略逐个替换Pod。maxSurge和maxUnavailable参数控制更新速度和可用性平衡。如果新Pod启动失败,滚动更新会自动暂停,旧Pod不会被终止,这比手动操作安全得多。
日志与监控:进程管理的眼睛进程管理做得好不好,要靠日志和监控来验证。Gunicorn的access日志记录每个请求的处理时间和状态码,配合--access-logfile和--error-logfile参数输出到文件或stdout。生产环境建议输出到stdout,让systemd或Docker收集,再用日志聚合工具统一处理。
关键指标要监控:worker重启频率(从Gunicorn日志里统计Booting worker消息)、请求延迟的P99值、5xx错误率、数据库连接池使用率。如果worker频繁重启,可能是内存泄漏或者某个接口有bug导致worker崩溃。如果请求延迟突然升高,检查是不是所有worker都在处理慢请求,新请求被排队。
Django可以用django-prometheus输出指标,Flask用prometheus-flask-exporter,Gunicorn本身也支持statsd协议输出指标。把这些指标接入Prometheus+Grafana,设置告警规则,进程管理就从被动救火变成主动预防。
总结选型建议Django项目推荐Gunicorn + UvicornWorker,既能用上异步IO,又保留Gunicorn成熟的进程管理。Flask项目如果IO密集用gevent worker,计算密集用sync worker加多进程。无论哪个框架,systemd做进程守护,HUP信号做平滑重启,pip-audit做漏洞扫描,这三样是标配。容器化部署的话,把进程管理交给编排系统,应用层只关注worker数量和健康检查。安全更新一定要走滚动部署流程,别图省事直接在线改代码,那是在给自己埋雷。
