1. 项目概述:为什么 Python Web 应用的服务器选择不是“装上就跑”那么简单

你写完一个 Flask 或 Django 的小 demo, python app.py 一执行,浏览器打开 http://localhost:5000 ,页面出来了——那一刻很爽。但如果你把这行命令直接扔进生产环境,等来的大概率不是用户好评,而是凌晨三点的告警短信、接口超时的客户投诉,以及运维同事发来的、带着三个感叹号的微信截图。这不是危言耸听,而是我过去十年在电商中台、SaaS 后台、AI API 服务等十多个 Python Web 项目里,亲手踩过、修过、重做过至少七次的坑。 Web 服务器不是应用的“搬运工”,而是它的“呼吸系统”和“免疫屏障” :它决定请求怎么进来、线程怎么调度、内存怎么分配、错误怎么兜底、流量高峰怎么扛住。选错服务器,轻则性能打五折、日志全乱码;重则进程静默崩溃、连接池耗尽、甚至被恶意请求拖垮整台机器。标题里这个“Comparison”,绝不是罗列几个名字、贴几行启动命令就能糊弄过去的。它背后是 WSGI 协议的底层契约、是 Unix 进程模型与异步 I/O 的博弈、是 Gunicorn 的预叉(pre-fork)如何对抗 uWSGI 的多模式混搭、更是 Nginx 作为反向代理时,与后端服务器之间那几十个关键参数的精密咬合。我见过团队用 Flask.run() 直接暴露在公网,结果被爬虫扫出 3000+ 并发,CPU 拉满到 98%,连 SSH 都连不上;也见过用默认配置的 uWSGI,在高并发下因 buffer-size 不足,导致 POST 大表单直接被截断,前端反复提交却收不到成功响应。所以这篇内容,不讲虚的“哪个最好”,只讲“在什么场景下,为什么必须选这个、不能选那个”。它适合刚部署完第一个 Django 项目的新人,也适合正为线上服务卡顿焦头烂额的资深工程师——因为问题从来不在代码本身,而在代码运行的那层“空气”里。

2. 核心设计思路拆解:从协议层到进程模型,看清每种服务器的“基因”

要真正理解 Gunicorn、uWSGI、Waitress、Daphne 等服务器的本质差异,必须先拨开“Python Web 服务器”这个统称的迷雾,回到最底层的契约: WSGI(Web Server Gateway Interface) 。它不是某个具体软件,而是一份 Python PEP 3333 文档定义的接口规范,核心就一句话: Web 服务器必须提供一个可调用对象(callable),接收 environ (环境字典)和 start_response (响应启动函数)两个参数,并返回一个可迭代的响应体(body) 。所有符合 WSGI 的服务器,本质上都是在扮演这个“中间人”角色:接收 HTTP 请求(解析成 environ )、调用你的应用(比如 app(environ, start_response) )、再把应用返回的 body 封装成标准 HTTP 响应发回客户端。这个看似简单的契约,却决定了所有服务器的底层行为逻辑。

2.1 WSGI 是“协议”,不是“实现”:为什么不能跳过它直接用 HTTP 服务器?

很多初学者会疑惑:“Nginx 不就是 HTTP 服务器吗?为什么还要加一层 Gunicorn?” 这是个极好的切入点。Nginx 是一个高性能的 HTTP 反向代理和负载均衡器 ,它擅长处理海量的静态文件、SSL 终止、请求路由、限流熔断。但它 完全不懂 Python 。它无法直接调用 app(environ, start_response) 这个 Python 函数。就像一个只会说英语的快递员(Nginx),要把包裹(HTTP 请求)送到一个只说中文的收件人(Python 应用)手里,中间必须有个翻译(WSGI 服务器)。Gunicorn、uWSGI 就是这个翻译,它们既懂 Nginx 的 HTTP 协议,又懂 Python 的 WSGI 接口。它们接收 Nginx 转发过来的原始 HTTP 数据,按 WSGI 规范组装成 environ 字典,再调用你的 Python 应用;应用处理完,它们再把返回的 body start_response 设置的 headers,重新打包成标准 HTTP 响应,交还给 Nginx。这个过程,我把它称为“协议翻译层”。跳过它,等于让英语快递员直接对着中文收件人比划,效率低下且极易出错。这也是为什么所有生产级 Python Web 架构,几乎清一色是 Nginx -> WSGI Server -> Python App 的三层结构,而非 Nginx -> Python App 的两层直连。

2.2 进程模型:预叉(Pre-fork)vs 多线程 vs 异步:性能差异的根源

当 WSGI 服务器拿到请求后,如何执行你的 Python 应用?这是性能分水岭。主流服务器采用三种根本不同的模型:

  • 预叉(Pre-fork)模型(Gunicorn 主力) :服务器启动时,先 fork 出一个主进程(master),再由 master 进程 fork 出多个工作进程(worker)。每个 worker 进程是独立的 Python 解释器实例,拥有自己的内存空间和 GIL(全局解释器锁)。当请求到来,master 进程通过 Unix socket 或 TCP socket 将请求分发给空闲的 worker。 优势 :进程隔离性极好,一个 worker 崩溃不会影响其他 worker;内存占用相对可控;对 CPU 密集型任务(如图像处理、复杂计算)友好,因为多进程能真正利用多核。 劣势 :每个 worker 都要加载一份完整的 Python 应用代码和依赖库,内存开销大;进程间通信(IPC)成本高;不适合高并发 I/O 密集型场景(如大量数据库查询、API 调用),因为每个 worker 在等待 I/O 时,整个进程都被阻塞,无法处理新请求。

  • 多线程模型(uWSGI 默认,Waitress 特色) :服务器启动一个主进程,该进程内创建多个线程(thread)。所有线程共享同一块内存空间(包括应用代码和全局变量),但每个线程有自己的栈。请求由主线程分发给空闲线程处理。 优势 :内存占用远低于预叉模型,因为代码和大部分数据只加载一次;线程间通信成本低。 劣势 :线程共享内存带来严重的线程安全问题,你的应用代码必须是线程安全的(比如避免全局可变状态);受 Python GIL 限制,多线程在 CPU 密集型任务上几乎无法提升性能,因为同一时刻只有一个线程能执行 Python 字节码;一个线程崩溃(如未捕获异常)可能导致整个进程退出。

  • 异步/协程模型(Daphne, Uvicorn, Hypercorn) :这是现代高性能服务器的代表。它基于 Python 的 asyncio 库,使用单个进程内的事件循环(event loop)来管理成千上万个协程(coroutine)。当一个协程需要等待 I/O(如数据库查询、HTTP 调用)时,它主动让出控制权( await ),事件循环立即将 CPU 时间片分配给另一个就绪的协程。 优势 :在 I/O 密集型场景(如微服务调用、实时消息推送)下,单机并发能力可达数万,内存占用极低;完美契合 ASGI(Asynchronous Server Gateway Interface)协议,是 Django Channels、FastAPI、Starlette 等异步框架的基石。 劣势 :要求你的应用代码必须是异步的( async def + await ),否则会阻塞整个事件循环;对同步阻塞操作(如 time.sleep(10) 、同步数据库驱动)零容忍;学习曲线陡峭,调试复杂。

提示:Gunicorn 的 --worker-class 参数可以切换为 gevent (基于 greenlet 的协程)或 eventlet ,但这只是在预叉模型上叠加了协程,其底层仍是多进程,与纯异步服务器有本质区别。uWSGI 则更激进,支持 --enable-threads (开启多线程)、 --processes (开启多进程)、 --async (开启异步)等多种模式混搭,灵活性极高,但也意味着配置复杂度指数级上升。

2.3 为什么没有“万能服务器”?场景决定一切

基于以上模型差异,我们可以清晰地画出一张选型地图:

  • 小型内部工具、个人博客、开发测试环境 Flask.run() Waitress 足够。Waitress 是纯 Python 实现,无 C 依赖,安装即用,线程模型对小流量足够稳定。
  • 传统 Django/Flask 项目,中等流量(日 PV 10w-100w),强调稳定性与易维护性 Gunicorn 是首选 。它的预叉模型简单、健壮、文档完善,社区支持强大。 gunicorn --bind :8000 --workers 4 --worker-class sync myapp:app 这条命令,覆盖了 80% 的生产需求。
  • 高并发、I/O 密集型 API 服务(如实时聊天、数据采集平台),且已采用 FastAPI/Starlette Uvicorn 是事实标准 。它专为 ASGI 设计,性能顶尖, uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 即可启动。
  • 超大型、混合负载(既有同步业务逻辑,又有异步长连接)、需要极致定制化与企业级功能(如动态重载、精细的内存监控、复杂的路由规则) uWSGI 是终极武器 。它像一个操作系统内核,提供了 --max-requests , --reload-on-rss , --harakiri , --cheaper 等上百个参数,能让你对每一个字节的内存、每一次请求的生命周期都了如指掌。但代价是,你需要花一周时间读完它的官方文档,才能写出一份不坑人的配置。

3. 核心细节解析与实操要点:Gunicorn 与 uWSGI 的配置密码

理论讲完,现在进入刀锋时刻。Gunicorn 和 uWSGI 的默认配置,就像一辆没调校过的赛车——引擎能转,但离赛道表现差得远。下面我将结合真实生产环境中的血泪教训,逐项拆解那些决定生死的关键参数。

3.1 Gunicorn:简洁背后的深意

Gunicorn 的哲学是“约定优于配置”,因此它的核心参数不多,但每个都至关重要。

  • --workers (工作进程数) :这是最常被误配的参数。很多人直接设为 --workers 8 ,认为越多越好。错! 理想值 = (CPU 核心数 * 2) + 1 。我的经验是:对于纯 CPU 密集型应用(如视频转码), workers = CPU 核心数 最优;对于典型的 Web 应用(混合 I/O 和 CPU), (CPU 核心数 * 2) + 1 是黄金公式。例如,一台 4 核服务器, --workers 9 是起点。但必须配合 --worker-class 使用: sync (默认,同步阻塞)适合大多数场景; gevent (需 pip install gevent )适合高并发 I/O,但要注意 gevent 的 monkey patching 可能与某些库冲突。

  • --worker-tmp-dir (工作进程临时目录) 这是个隐藏的性能杀手 。Gunicorn 默认将临时文件(如上传的大文件缓存)放在 /tmp 下。如果 /tmp 是内存盘(tmpfs),没问题;但如果 /tmp 是普通磁盘,且你的应用频繁处理大文件上传, /tmp 目录会成为 I/O 瓶颈,甚至填满磁盘。 实操心得 :永远显式指定一个高速、大容量的磁盘路径,如 --worker-tmp-dir /data/gunicorn-tmp ,并确保该目录有足够权限和空间。

  • --timeout --keep-alive --timeout 30 表示一个 worker 处理单个请求的最长时限,超时则被 master 杀死并重启。这能防止一个慢请求拖垮整个 worker。但注意,这个 timeout 是针对“处理时间”,不包括网络传输时间。 --keep-alive 5 则设置 HTTP Keep-Alive 的超时时间(秒),即连接空闲多久后关闭。设为 0 表示禁用 Keep-Alive,会极大增加 TCP 握手开销;设为 5-30 是合理范围,取决于你的客户端特性(如移动端网络不稳定,可设高些)。

  • --preload (预加载) :默认情况下,Gunicorn 的每个 worker 进程会独立加载你的应用模块( myapp:app )。这意味着每个 worker 都要执行一遍 import 、初始化数据库连接池、加载配置文件。这不仅慢,还浪费内存。 --preload 参数会让 master 进程在 fork 所有 worker 之前,先加载一次应用,然后 fork 出的 worker 会继承这个已加载的内存镜像。 效果立竿见影 :启动时间缩短 50%,内存占用降低 30%。但有一个致命前提:你的应用代码必须是“fork-safe”的,即不能在模块顶层执行任何不可 fork 的操作(如创建线程、打开文件句柄、初始化某些 C 扩展)。我曾在一个项目中启用 --preload 后,所有 worker 启动失败,排查三天才发现是某个第三方库在 __init__.py 里偷偷启动了一个后台线程。

注意:Gunicorn 的 --reload 模式(开发用)与 --preload 冲突,切勿在生产环境混用。

3.2 uWSGI:功能巨兽的驾驭指南

uWSGI 的配置堪称“Linux 内核级”,其 .ini 配置文件动辄上百行。这里只提炼最核心、最易出错的五个模块。

  • [uwsgi] 核心模块

    • processes = 4 :等同于 Gunicorn 的 --workers ,但 uWSGI 更进一步,支持 cheaper 子系统: cheaper = 2 表示最少保持 2 个 worker; cheaper-algo = busyness 表示根据 worker 的繁忙程度(CPU/内存使用率)动态伸缩 worker 数量。这在流量波峰波谷明显的场景(如电商大促)下,能节省 40% 的内存。
    • threads = 2 :开启多线程,每个 worker 进程内再创建 2 个线程。这相当于“进程+线程”混合模型,兼顾了进程隔离与内存效率。但必须配合 enable-threads = true master = true (开启 master 进程管理)。
  • [uwsgi] 网络与协议模块

    • http = :8000 :让 uWSGI 直接监听 HTTP 端口。 强烈不推荐! 这会绕过 Nginx,失去 SSL 终止、静态文件服务、高级负载均衡等所有企业级能力。正确姿势是 socket = /tmp/uwsgi.sock (Unix socket)或 socket = 127.0.0.1:8000 (TCP socket),再由 Nginx 通过 proxy_pass 转发。
    • chmod-socket = 664 :设置 socket 文件的权限。如果权限不对,Nginx 会因“Permission denied”无法连接。 664 表示 owner/group 可读写,others 可读,通常 www-data 用户组需要这个权限。
  • [uwsgi] 安全与稳定性模块

    • harakiri = 30 :这是 uWSGI 的“自杀式看门狗”。如果一个请求处理时间超过 30 秒,uWSGI 会强制杀死该 worker 进程,防止其无限期挂起。这比 Gunicorn 的 --timeout 更激进,因为它直接杀进程,而非优雅终止。
    • reload-on-rss = 1024 :当一个 worker 进程的 RSS(常驻内存集)超过 1024MB 时,自动重启它。这是对抗内存泄漏的终极手段。我曾用它救活一个因 ORM 缓存 bug 导致内存持续增长的服务,让它稳定运行了三个月。
  • [uwsgi] 日志与监控模块

    • logto = /var/log/uwsgi/myapp.log :指定日志文件。 务必开启! 默认日志输出到 stdout,会被 systemd 或 Docker 捕获,但难以做日志轮转和分析。
    • stats = 127.0.0.1:9191 :开启内置的统计服务器,可通过 curl http://127.0.0.1:9191 获取 JSON 格式的实时状态(worker 数量、请求队列、内存使用等)。这是你做自动化监控和告警的数据源。
  • [uwsgi] 高级特性模块

    • vacuum = true :当 uWSGI 退出时,自动清理所有创建的 socket 文件、pid 文件等临时资源。不加这个,服务器重启后可能因 socket 文件残留而启动失败。
    • die-on-term = true :当收到 SIGTERM 信号(如 systemctl stop )时,uWSGI 会优雅地等待所有 worker 处理完当前请求后再退出。这是保证零停机发布(Zero-Downtime Deployment)的关键。

3.3 Nginx 与后端服务器的“握手协议”:502 Bad Gateway 的真相

90% 的 502 Bad Gateway 错误,根源不在 Python 应用,而在 Nginx 与 Gunicorn/uWSGI 的连接配置失配。这是一个经典的“三明治”故障点。

  • 超时时间链 :Nginx 的 proxy_read_timeout 必须大于 uWSGI 的 harakiri 或 Gunicorn 的 --timeout 。例如,uWSGI harakiri = 30 ,那么 Nginx 必须设置 proxy_read_timeout 60; 。否则,Nginx 在 30 秒后就断开了与 uWSGI 的连接,而 uWSGI 还在努力处理请求,最终返回一个无效响应,Nginx 就报 502

  • 缓冲区大小 :当你的应用返回超大响应体(如导出百万行 CSV),或接收超大请求体(如上传 2GB 视频),Nginx 的默认缓冲区( proxy_buffer_size , proxy_buffers )会不够用,导致 502 413 Request Entity Too Large 。解决方案是显式增大:

    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;
    client_max_body_size 2G; # 允许客户端上传最大 2G
    
  • 连接复用 :Nginx 默认会复用与后端服务器的连接( keepalive ),以减少 TCP 握手开销。但 uWSGI/Gunicorn 的 socket 连接池数量有限。如果 Nginx 的 keepalive 连接数超过了后端的 max-connections ,就会出现连接拒绝。因此,Nginx 的 upstream 块中, keepalive 的值应略小于后端的 worker 数量。例如,Gunicorn 有 4 个 worker,Nginx 可设 keepalive 3;

4. 实操过程与核心环节实现:从零搭建一个抗压的 Django 生产环境

现在,我们把所有理论揉进一个真实的、可立即复现的完整流程。目标:在一台 4 核 8G 的 CentOS 7 服务器上,部署一个 Django 项目,使其能稳定承载 5000 QPS 的并发请求。整个过程分为四步:环境准备、应用打包、服务器配置、Nginx 整合。

4.1 环境准备:干净、隔离、可重现

第一步永远是环境。我坚决反对在系统 Python 下直接 pip install ,因为这会导致依赖污染和升级灾难。

  1. 安装 pyenv(Python 版本管理器)

    # 安装依赖
    yum install -y make build-essential libssl-dev zlib1g-dev \
    libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
    libncurses5-dev libncursesw5-dev xz-utils tk-dev
    
    # 安装 pyenv
    curl https://pyenv.run | bash
    # 将以下三行加入 ~/.bashrc
    export PYENV_ROOT="$HOME/.pyenv"
    command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
    eval "$(pyenv init -)"
    source ~/.bashrc
    
  2. 安装并切换 Python 版本

    pyenv install 3.10.12
    pyenv global 3.10.12
    python --version # 应输出 3.10.12
    
  3. 创建虚拟环境并安装依赖

    # 创建名为 'myproject' 的虚拟环境
    python -m venv /opt/myproject/venv
    # 激活
    source /opt/myproject/venv/bin/activate
    # 升级 pip
    pip install --upgrade pip
    # 安装项目依赖(假设 requirements.txt 已准备好)
    pip install -r /opt/myproject/requirements.txt
    # 特别安装 Gunicorn
    pip install gunicorn
    

实操心得: /opt/myproject 是 Linux 下存放生产应用的标准路径,比 /home/user/project 更符合系统管理规范。 venv 目录必须与项目代码分离,便于版本管理和权限控制。

4.2 Django 应用配置:为生产而生

Django 的 settings.py 在开发和生产环境下必须严格区分。

  1. 环境变量驱动配置 :创建 settings/base.py (通用配置)、 settings/production.py (生产专用)。在 production.py 中:

    from .base import *
    
    DEBUG = False
    SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY') # 从环境变量读取,绝不硬编码
    
    # 数据库配置(使用 PostgreSQL,比 SQLite 更健壮)
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': os.environ.get('DB_NAME', 'myproject'),
            'USER': os.environ.get('DB_USER', 'myuser'),
            'PASSWORD': os.environ.get('DB_PASSWORD', ''),
            'HOST': os.environ.get('DB_HOST', '127.0.0.1'),
            'PORT': '5432',
        }
    }
    
    # 静态文件(CSS/JS/Images)由 Nginx 直接服务,Django 不处理
    STATIC_ROOT = '/opt/myproject/staticfiles'
    STATIC_URL = '/static/'
    
    # 媒体文件(用户上传)同样由 Nginx 服务
    MEDIA_ROOT = '/opt/myproject/media'
    MEDIA_URL = '/media/'
    
    # 允许的域名(防止 Host Header 攻击)
    ALLOWED_HOSTS = ['myproject.com', 'www.myproject.com']
    
    # 日志配置:将所有日志输出到文件,而非 console
    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            'file': {
                'level': 'INFO',
                'class': 'logging.handlers.RotatingFileHandler',
                'filename': '/var/log/myproject/django.log',
                'maxBytes': 1024*1024*5, # 5MB
                'backupCount': 5,
            },
        },
        'loggers': {
            'django': {
                'handlers': ['file'],
                'level': 'INFO',
                'propagate': True,
            },
        },
    }
    
  2. 收集静态文件

    # 激活虚拟环境
    source /opt/myproject/venv/bin/activate
    # 进入项目目录
    cd /opt/myproject
    # 收集所有静态文件到 STATIC_ROOT
    python manage.py collectstatic --noinput
    # 此时 /opt/myproject/staticfiles 下会有所有 CSS/JS 文件
    

4.3 Gunicorn 配置:编写一个健壮的启动脚本

创建 /etc/systemd/system/gunicorn.service ,这是 systemd 管理 Gunicorn 的“宪法”。

[Unit]
Description=Gunicorn daemon for myproject
Requires=gunicorn.socket
After=network.target

[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/opt/myproject
ExecStart=/opt/myproject/venv/bin/gunicorn \
          --access-logfile /var/log/myproject/gunicorn_access.log \
          --error-logfile /var/log/myproject/gunicorn_error.log \
          --log-level info \
          --bind unix:/run/gunicorn.sock \
          --bind 127.0.0.1:8001 \
          --workers 9 \
          --worker-class sync \
          --timeout 120 \
          --keep-alive 5 \
          --max-requests 1000 \
          --max-requests-jitter 100 \
          --preload \
          --worker-tmp-dir /tmp \
          --chdir /opt/myproject \
          myproject.wsgi:application

# 重启策略
Restart=always
RestartSec=10

# 环境变量
EnvironmentFile=/opt/myproject/.env

[Install]
WantedBy=multi-user.target

关键参数详解

  • Type=notify :Gunicorn 启动后会向 systemd 发送 READY=1 信号,告知已就绪。这比 Type=simple 更可靠。
  • --bind unix:/run/gunicorn.sock :使用 Unix socket,比 TCP socket 性能更高、更安全。 /run 是 tmpfs 内存盘,速度飞快。
  • --max-requests 1000 :每个 worker 处理 1000 个请求后自动重启,防止内存泄漏累积。
  • --max-requests-jitter 100 :在 1000±100 的范围内随机重启,避免所有 worker 同时重启造成服务抖动。
  • EnvironmentFile=/opt/myproject/.env :将 DJANGO_SETTINGS_MODULE=myproject.settings.production 等敏感配置放入 .env 文件,与代码分离。

启动服务

# 重载 systemd 配置
sudo systemctl daemon-reload
# 启用开机自启
sudo systemctl enable gunicorn
# 启动
sudo systemctl start gunicorn
# 查看状态
sudo systemctl status gunicorn

4.4 Nginx 整合:最后的临门一脚

创建 /etc/nginx/sites-available/myproject

upstream django_app {
    # 指向 Gunicorn 的 Unix socket
    server unix:/run/gunicorn.sock fail_timeout=0;
    # 可以添加第二个 Gunicorn 实例做负载均衡
    # server 127.0.0.1:8001 fail_timeout=0;
}

server {
    listen 80;
    server_name myproject.com www.myproject.com;

    # 重定向 HTTP 到 HTTPS(生产环境必须)
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name myproject.com www.myproject.com;

    # SSL 证书(使用 Let's Encrypt)
    ssl_certificate /etc/letsencrypt/live/myproject.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myproject.com/privkey.pem;

    # 安全加固
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # 静态文件由 Nginx 直接服务
    location /static/ {
        alias /opt/myproject/staticfiles/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # 媒体文件由 Nginx 直接服务
    location /media/ {
        alias /opt/myproject/media/;
        expires 1y;
    }

    # 所有其他请求转发给 Django
    location / {
        include proxy_params;
        proxy_pass http://django_app;
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 关键:超时设置必须大于 Gunicorn 的 timeout
        proxy_connect_timeout 60s;
        proxy_send_timeout 120s;
        proxy_read_timeout 120s;

        # 开启连接复用
        proxy_http_version 1.1;
        proxy_set_header Connection '';
        proxy_keepalive_requests 1000;
        proxy_keepalive_timeout 60s;
    }
}

启用站点并重启 Nginx

sudo ln -sf /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/
sudo nginx -t # 测试配置
sudo systemctl restart nginx

至此,一个完整的、可投入生产的 Django 环境就搭建完成了。你可以用 ab (Apache Bench)或 wrk 工具进行压力测试:

wrk -t12 -c400 -d30s http://myproject.com/

观察 htop 中的 CPU、内存,以及 /var/log/myproject/gunicorn_access.log 中的响应时间,验证其稳定性。

5. 常见问题与排查技巧实录:那些年我们一起追过的 502 和 Segmentation Fault

再完美的配置,在真实世界里也会遇到各种“惊喜”。以下是我在生产环境中记录下来的、最高频、最棘手的五个问题,以及我总结出的、经过千锤百炼的排查路径。

5.1 问题一: 502 Bad Gateway —— Nginx 的无声控诉

现象 :浏览器访问,Nginx 返回 502 Bad Gateway /var/log/nginx/error.log 中出现 connect() to unix:/run/gunicorn.sock failed (111: Connection refused) upstream timed out (110: Connection timed out)

排查路径

  1. 确认 Gunicorn 是否在运行 sudo systemctl status gunicorn 。如果显示 inactive (dead) ,直接 sudo systemctl start gunicorn
  2. 确认 socket 文件是否存在且权限正确 ls -l /run/gunicorn.sock 。正常应显示 srw-rw-rw- 1 www-data www-data ... 。如果文件不存在,检查 Gunicorn 的 --bind 参数是否指向了 /run/gunicorn.sock ;如果权限不对(如 root 所有),检查 gunicorn.service 中的 User Group 是否为 www-data
  3. 检查 Nginx 的 upstream 地址是否匹配 cat /etc/nginx/sites-enabled/myproject | grep "server unix" ,确认它指向的 socket 路径与 Gunicorn 的 --bind 完全一致。
  4. 检查超时时间链 sudo tail -f /var/log/nginx/error.log ,如果看到 upstream timed out ,立刻检查 Nginx 的 proxy_read_timeout 是否大于 Gunicorn 的 --timeout 。这是 90% 的原因。

实操心得:我写了一个一键诊断脚本 check_web.sh ,它会自动检查 Gunicorn 状态、socket 文件、Nginx 配置语法、端口监听情况,并输出一个清晰的报告。每次上线前运行它,能省下 80% 的排障时间。

5.2 问题二: 504 Gateway Timeout —— 请求在半路“失踪”

现象 :Nginx 返回 504 Gateway Timeout error.log 中有 upstream timed out (110: Connection timed out)

根本原因 :这不是连接失败,而是连接成功了,但后端(Gunicorn/uWSGI)花了太长时间才返回响应,超过了 Nginx 的耐心。

排查路径

  1. 定位慢请求 sudo tail -f /var/log/myproject/gunicorn_access.log ,找到响应时间( "-" 字段后的数字)特别大的那一行,记下其 URL 和时间戳。
  2. 检查应用日志 sudo tail -f /var/log/myproject/django.log ,在同一时间戳附近,查找是否有数据库查询慢、外部 API 调用超时、或死循环的日志。
  3. 检查数据库连接池 :Django 的 CONN_MAX_AGE 如果设为 0 (每次请求新建连接),在高并发下会耗尽数据库连接。应设为 60 (秒),并确保数据库的 max_connections 足够大。
  4. 检查 Gunicorn 的 --timeout :如果 --timeout 设得太小(如 10 ),而你的业务逻辑本身就需 15 秒,那 Gunicorn 会在 10 秒时杀死 worker,Nginx 等待超时后就报 504 。此时应调大 --timeout ,并优化业务逻辑。

5.3 问题三: Segmentation Fault (core dumped) —— Python 的“蓝屏死机”

现象 :Gunicorn 或 uWSGI 进程突然崩溃, systemctl status 显示 failed journalctl -u gunicorn 中出现 Segmentation fault

根本原因 :这是 C 扩展层面的崩溃,通常由以下原因引起:

  • 使用了不兼容的 C 扩展(如旧版 psycopg2 与新 PostgreSQL 不兼容)。
  • 内存损坏(如某个库的 bug)。
  • --preload 模式下,应用代码中存在 fork-unsafe 操作

更多推荐