总览

不废话, 先看表格

组件 协议(向上提供服务) 协议(向下转发) 备注
Nginx HTTP HTTP, uwsgi unix socket对位的是TCP
Gunicorn HTTP WSGI  
uWSGI HTTP, uwsgi WSGI 太老旧, 废弃
uWSGI是一个服务器, uwsgi是他的协议
Nginx的uwsgi_pass可对接
Uvicorn HTTP ASGI  
Django/Flask WSGI   WSGI is a Python standard, 不是通信
直接进程内import, 函数调用
另外Django自带一个开发用http服务
FastAPI(Starlette) ASGI   ASGI也一样, 区别是异步

根据表格中的协议, 以上组件可以组合出n种部署方案.

同步应用部署方案:

  • 🚧Django (runserver基于wsgiref)
  • ❌Gunicorn → Django
  • ✅Nginx → Gunicorn → Django
  • ❌uWSGI → Django
  • ❌Nginx → uWSGI → Django

异步应用部署方案:

  • 🚧Uvicorn → FastAPI
  • ❌Gunicorn → Uvicorn → FastAPI
  • ❌Nginx → Uvicorn → FastAPI
  • ✅Nginx → Gunicorn → Uvicorn → FastAPI

🚧是本地开发调试用的 ✅是推荐的线上方案 ❌是不推荐

真的挺混乱的, 其实还有更多类似的组件和库没放进去呢.

分类和简化一下上述概念可以得出, 走网络的通信协议:

  • HTTP
  • uwsgi

进程内部python函数调用标准:

  • WSGI
  • ASGI

Gunicorn

如果是基于WSGI的同步应用, Gunicorn是最佳选项. Gunicorn提供的核心功能

  • 负责接受和解析HTTP
  • WSGI协议支持
  • 多worker进程管理(pre-fork worker model)

Gunicorn和Django

  • worker进程就和Django是同一个进程
  • Gunicorn和Django之间是WSGI, 不用走网络请求了
  • 多个进程监听同一端口? 是的. 通过共享socket或者reuseport功能.

WSGI

一个简单函数:

def simple_app(environ, start_response):
    status = '200 OK'
    headers = [('Content-Type', 'text/plain')]
    start_response(status, headers)
    return [b"Hello, WSGI!"]

# 使用 wsgiref 内置服务器运行
from wsgiref.simple_server import make_server
httpd = make_server('', 8000, simple_app)
print("Serving on port 8000...")
httpd.serve_forever()

说明:

  • environ: 一个包含请求信息的字典(如路径、请求方法等)
  • start_response(status, headers): 一个函数,用于发送HTTP响应状态和头部信息
  • 返回值:必须是一个 可迭代对象,其中每个元素是bytes

Nginx和Gunicorn

  • Gunicorn独自就能干活了, 为啥要加一层Nginx?

    根据SO上的问题, 大部分生产级复杂系统, 都有很多不应该交由Python来响应的请求, 如静态资源, 这就需要加一层Nginx.

  • Nginx会不会将请求转换为wsgi再发给应用服务呢?

    不会, Nginx又不是python, 调用不了python方法, 没实现wsgi; uwsgi一字之差啊;

  • Nginx和Gunicorn之间的通信?

    简单的HTTP转发关系, 同一台机器上使用unix socket连接, unix socket对位的是TCP.

  • 他俩谁会解析HTTP请求?

    都会. Nginx出于转发规则的目的, 也要知道HTTP请求路径之类的信息. Gunicorn则要完整解析HTTP请求, 之后通过WSGI转给Django.

Nginx → Gunicorn → Django 完整流程图👍🏻

 graph TD
    a1["Django"]
    a2["Django"]
    a3["Django"]
    Client -->|HTTP| Nginx
    subgraph Nginx
		    Port-80 -->|HTTP| NginxWorker-1
		    Port-80 -->|HTTP| NginxWorker-2
		    Port-80 -->|HTTP| NginxWorker-N
    end
    Nginx -->|HTTP| Gunicorn
    subgraph Gunicorn
		    Port-8080 -->|HTTP| GunicornSyncWorker-1
		    Port-8080 -->|HTTP| GunicornSyncWorker-2
		    Port-8080 -->|HTTP| GunicornSyncWorker-N
        subgraph "Gunicorn-N"
            GunicornSyncWorker-N -->|WSGI| a3
        end
        subgraph "Gunicorn-2"
            GunicornSyncWorker-2 -->|WSGI| a2
        end
        subgraph "Gunicorn-1"
            GunicornSyncWorker-1 -->|WSGI| a1
        end
    end

    desc1["实线矩形是进程"]

style desc1 fill:#fff,stroke:#fff,color:#555,font-style:italic
style Port-8080 fill:#f0f0f0,stroke:#ccc,stroke-dasharray: 5 5,color:#999
style Port-80 fill:#f0f0f0,stroke:#ccc,stroke-dasharray: 5 5,color:#999
style Gunicorn stroke:#ccc,stroke-dasharray: 5 5,color:#999
style Nginx stroke:#ccc,stroke-dasharray: 5 5,color:#999
style GunicornSyncWorker-1 stroke:#ccc,stroke-dasharray: 5 5,color:#999
style GunicornSyncWorker-2 stroke:#ccc,stroke-dasharray: 5 5,color:#999
style GunicornSyncWorker-N stroke:#ccc,stroke-dasharray: 5 5,color:#999
style a1 stroke:#ccc,stroke-dasharray: 5 5,color:#999
style a2 stroke:#ccc,stroke-dasharray: 5 5,color:#999
style a3 stroke:#ccc,stroke-dasharray: 5 5,color:#999

Uvicorn

实现了ASGI的应用服务器

ASGI

一个async函数

async def app(scope, receive, send):
    if scope["type"] == "http":
        event = await receive()
        body = event.get("body", b"")
        await send({
            "type": "http.response.start",
            "status": 200,
            "headers": [(b"content-type", b"text/plain")],
        })
        await send({
            "type": "http.response.body",
            "body": body,
        })

说明:

  • receive: 等待客户端发送事件的协程函数
  • send: 向客户端发送事件的协程函数
  • scope: 请求的上下文信息(如类型、路径、headers)

      {
          "type": "http",
          "method": "GET",
          "path": "/",
          "headers": [(b"host", b"example.com")]
      }
    
特性 WSGI ASGI
是否异步支持 ❌ 不支持(同步阻塞) ✅ 原生支持异步与并发
支持 WebSocket ✅ 支持
服务端模型 线程/进程 协程(事件循环)

Uvicorn和Gunicorn

Uvicorn已经能提供http服务了, 为啥还用Gunicorn:

  • Gunicorn 的进程管理能力: Gunicorn 具有强大的进程管理能力,可以监控 worker 进程的运行状态,并在进程崩溃时自动重启。这对于保证应用的稳定性和可靠性非常有帮助。
  • Uvicorn 的高性能: Uvicorn 在处理异步应用时具有很高的性能,可以有效地提高应用的吞吐量和响应速度。

Nginx → Gunicorn → Uvicorn → FastAPI 完整流程图👍🏻

graph TD
    a1["FastAPI"]
    a2["FastAPI"]
    a3["FastAPI"]
    Client -->|HTTP| Nginx
    subgraph Nginx
		    Port-80 -->|HTTP| NginxWorker-1
		    Port-80 -->|HTTP| NginxWorker-2
		    Port-80 -->|HTTP| NginxWorker-N
    end
    Nginx -->|HTTP| Gunicorn
    subgraph Gunicorn
		    Port-8080 -->|HTTP| UvicornWorker-1
		    Port-8080 -->|HTTP| UvicornWorker-2
		    Port-8080 -->|HTTP| UvicornWorker-N
        subgraph "Gunicorn-N"
            UvicornWorker-N -->|ASGI| a3
        end
        subgraph "Gunicorn-2"
            UvicornWorker-2 -->|ASGI| a2
        end
        subgraph "Gunicorn-1"
            UvicornWorker-1 -->|ASGI| a1
        end
    end

    desc1["实线矩形是进程"]

style desc1 fill:#fff,stroke:#fff,color:#555,font-style:italic
style Port-8080 fill:#f0f0f0,stroke:#ccc,stroke-dasharray: 5 5,color:#999
style Port-80 fill:#f0f0f0,stroke:#ccc,stroke-dasharray: 5 5,color:#999
style Gunicorn stroke:#ccc,stroke-dasharray: 5 5,color:#999
style Nginx stroke:#ccc,stroke-dasharray: 5 5,color:#999
style UvicornWorker-1 stroke:#ccc,stroke-dasharray: 5 5,color:#999
style UvicornWorker-2 stroke:#ccc,stroke-dasharray: 5 5,color:#999
style UvicornWorker-N stroke:#ccc,stroke-dasharray: 5 5,color:#999
style a1 stroke:#ccc,stroke-dasharray: 5 5,color:#999
style a2 stroke:#ccc,stroke-dasharray: 5 5,color:#999
style a3 stroke:#ccc,stroke-dasharray: 5 5,color:#999
距离上一篇文章又隔了一年多了啊, 又在瞎忙, 没啥内容产出