← 返回 Visuals 首页
先上结论:这三个都是应用服务器(Application Server)——站在 Web 框架(Django / Flask / Rails)前面,负责管进程、管并发、收 HTTP 请求、调你的应用代码、再把响应发回去。框架本身不管这些生产级脏活。它们和上一页的 Redis/epoll 是两种不同流派:Redis 用「单线程 + epoll 事件循环」;而 Gunicorn/Unicorn 默认用「多进程(pre-fork) + 阻塞 IO」。
🧱 一个典型 Python Web 部署架构
→
🚪 Nginx
反向代理 · TLS · 静态 · 挡慢客户端
→
→
🐍 你的 App
Django / Flask (WSGI)
为什么框架不能直接裸奔?
- 并发与进程管理:Flask/Django 自带的 dev server 只能处理很少并发,且不稳定。生产需要「开多个进程/线程」来扛量。
- 协议处理:要正确解析 HTTP、处理 keep-alive、大请求体、慢客户端。
- 健壮性:某个请求崩了不能拖垮整个服务;worker 挂了要能自动拉起(master 监管)。
- WSGI / Rack 接口:应用服务器按标准调用你的
app(environ, start_response),框架只管写业务逻辑。
# WSGI 约定:你的应用就是一个可被调用的函数
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/plain')])
return [b'Hello']
# Gunicorn/uWSGI 加载它,并为每个 HTTP 请求调用一次
下一节先看清三者分别是什么,再深挖它们的网络 IO 实现差异。
三者常被一起比较,但有一个关键误会要先拆穿:Unicorn 是 Ruby 的应用服务器,不是 Python 的。Gunicorn 全称「Green Unicorn」,正是受 Unicorn 启发、用 Python 写的同类产品。所以准确说:Python 阵营是 Gunicorn + uWSGI;Unicorn 是它们的 Ruby 表亲。但三者架构思想同源(pre-fork),正好一起讲。
🟦uWSGI C 语言
功能最全的「应用服务器全家桶」,C 写的,性能强、配置项极多,支持 HTTP/uwsgi/FastCGI 等协议与进程+线程+异步。
点开看定位 →
🟩Gunicorn Python
纯 Python 的 WSGI 服务器,pre-fork 模型,配置简单。worker 类型可换(sync/gthread/gevent),最常用。
点开看定位 →
🟪Unicorn Ruby
Ruby 的 Rack 服务器,pre-fork 模型,每个 worker 一次只处理一个请求,刻意不做线程/异步,靠多进程扩展。
点开看定位 →
uWSGI:应用服务器里的「瑞士军刀」
- 用 C 编写,性能高、内存占用低,功能覆盖极广(不只是 Python,还支持 Ruby/PHP/Perl/Go…)。
- 自带协议 uwsgi(二进制,比 HTTP 解析更省),通常放在 Nginx 后面用
uwsgi_pass 通信。
- 并发模型最丰富:多进程 + 每进程多线程 + 异步核心(ugreen),一套配置全搞定。
- 代价:配置项多到劝退,学习曲线陡;但一旦配好,生产表现非常稳。
Gunicorn:Python 世界的「刚刚好」
- 纯 Python,WSGI-only,开箱即用,一条命令启动。
- 核心是 pre-fork master + N 个 worker;worker 类型可切换:
sync(默认,1 请求/worker)、gthread(线程池)、gevent/eventlet(协程事件循环)。
- 默认直接说 HTTP(一般仍放 Nginx 后),不强制用自有协议。
- 哲学:简单、够用、Pythonic;复杂需求靠换 worker 类型解决。
Unicorn:Ruby 的极简 pre-fork 服务器
- 纯 Ruby,面向 Rack(Ruby 的 WSGI 等价物),由 Mongrel 作者开发。
- 设计哲学是「能多简单就多简单」:master fork 出 worker,每个 worker 一次只处理一个请求,用阻塞 IO,故意不做线程、不做异步。
- 靠「多跑几个 worker 进程」来扩展并发;因为 Ruby 有 GVL,线程对 CPU 密集帮助有限,作者认为不如多进程清晰。
- Gunicorn 基本是 Unicorn 的 Python 翻版——所以你理解了一个,另一个就通了。
三者速查总表
| 维度 | uWSGI | Gunicorn | Unicorn |
| 语言 | C | Python | Ruby |
| 接口标准 | WSGI / Rack / PSGI… | WSGI | Rack |
| 自家协议 | uwsgi(二进制) | HTTP | HTTP |
| 并发模型 | 进程+线程+异步 | 进程(pre-fork)+可选线程/协程 | 进程(pre-fork),1请求/worker |
| 配置难度 | 高(项极多) | 低(一条命令) | 低 |
| 典型定位 | 极致性能/多功能 | Python 默认首选 | Ruby 默认首选 |
现在进核心:它们怎么实现网络 IO? 关键要和上页的 epoll 对照着看。Gunicorn/Unicorn 默认走的是 pre-fork 多进程 + 阻塞 IO——这跟 Redis 那种「单线程事件循环」是两条完全不同的路,但都解决了「海量连接」。
pre-fork 模型:master 生 worker,worker 各自阻塞干活
👑 Master
启动 · fork worker
· 监听端口(socket)
· worker 挂了自动拉起
Worker 1
accept → 阻塞读 → 调App → 写回
Worker 2
accept → 阻塞读 → 调App → 写回
Worker 3
accept → 阻塞读 → 调App → 写回
📡 监听 socket 由 master 创建,fork 后每个 worker 继承同一个 fd,各自 accept() 抢连接
- master 进程只管「生孩子 + 监管」:启动时建好监听 socket,然后
fork() 出 N 个 worker,把 socket fd 继承给它们。
- 每个 worker 是个独立进程,跑一个循环:
accept() 抢到一个连接 → 阻塞读完整个请求 → 调你的 WSGI app → 把响应写回 → 再 accept 下一个。
- 并发度 = worker 数量。一个 worker 同时只服务一个连接,但 N 个 worker 就并发 N 个(靠 OS 调度 + 多核)。
- 注意:这里 worker 内部没有 epoll 事件循环——它直接用阻塞 accept/read,复杂度低、最稳。
和上一页 epoll 的对照(重要)
| 方案 | 线程/进程数 | IO 方式 | 就绪通知 | 代表 |
| epoll 事件循环 | 少(常 1 个) | 非阻塞 + 事件驱动 | 内核 epoll 通知 | Redis、Nginx |
| pre-fork 阻塞 | 多(=连接并发) | 阻塞 accept/read | 不需要,进程各干各的 | Gunicorn(sync)、Unicorn |
| 线程池 | 少进程×多线程 | 阻塞,但 GIL 释放 | 不需要 | Gunicorn(gthread)、uWSGI(threads) |
| 协程事件循环 | 少进程×多协程 | 非阻塞 + 事件驱动 | libev/epoll(绿线程) | Gunicorn(gevent)、uWSGI(async) |
uWSGI 的「混合」模型 & Gunicorn 的 gevent 桥
桥接点:Gunicorn 的 gevent worker 和 uWSGI 的 async 核心,其实就是把上一页的 epoll 事件循环搬进了 worker 里——它们对 socket 做 monkey-patch 变成非阻塞,底层用 libev(epoll/kqueue)驱动成千上万个「绿线程(协程)」。所以「Gunicorn 用了 epoll」这句话只对 gevent/eventlet worker 成立;默认的 sync worker 完全没用 epoll。
- uWSGI:在 pre-fork 基础上,每个 worker 还能开
--threads 个线程,或开 --async 个异步核心——单 worker 就能并发很多连接。
- Gunicorn:默认 sync(1 请求/worker);换 gthread 加线程;换 gevent 进事件循环。灵活切换,代价是 gevent 要 monkey-patch,某些库不兼容。
- Unicorn:坚持纯 pre-fork 阻塞,不提供线程/异步选项——它的信条是「简单即可靠,扩展靠堆进程」。
光讲抽象不够,来动手比一比:选不同服务器/worker 类型,调 worker 数和线程数,看「同时能扛多少请求」。你会直观看到 pre-fork(进程数决定并发)和异步(1 进程扛几千)的天壤之别。
说明:gevent/uWSGI 异步每 worker 按「数千并发」估算(协程极轻);sync/unicorn 严格 1 请求/worker;gthread/uWSGI 线程 = worker×线程。超出处理能力的请求会排队(橙色),拖慢响应。
同一时刻来了 3 个请求,阻塞式 worker 和 协程事件循环 worker 的处理节奏完全不同。点「播放」看时间轴上的差别——这正是「为什么 IO 密集用异步」的直观原因。
🐢 阻塞式(sync / Unicorn)
总耗时 ≈ A+B+C(串行,一个 worker 一次只干一个)
🐇 协程式(gevent / uWSGI async)
总耗时 ≈ max(A,B,C)(重叠,等 A 的 DB 时去处理 B/C)
原理:阻塞式 worker 在「查数据库 / 调外部 API」这种 IO 等待期间,整个进程被卡住,只能干等;协程式 worker 在 IO 等待时把执行权让给别的协程,等 IO 好了再回来——于是 3 个请求的等待时间被「重叠」掉了。注意:这靠的是单进程内的协程切换(由 epoll 驱动),和多进程无关。
无论选哪个应用服务器,生产环境几乎都把它放在 Nginx 后面。原因不只是「习惯」,而是 Nginx 帮应用服务器挡掉了很多它不擅长/不该干的活。
为什么前面要 Nginx?
- TLS 终止:HTTPS 加解密交给 Nginx,应用服务器只跑明文 HTTP,省 CPU。
- 静态文件 & 缓冲:图片/CSS/JS 由 Nginx 直接返回,不劳烦 Python/Ruby。
- 挡慢客户端(防 Slowloris):Nginx 慢慢收完客户端慢吞吞的请求体,再一次性传给后端——后端不用被慢连接占着。
- 负载均衡 & 多后端:一个 Nginx 后面可以挂多个 Gunicorn/uWSGI 实例。
- 重生重启不停服:Nginx 常驻,后端滚动重启不影响对外。
协议差异:uWSGI 协议 vs HTTP
| 通信段 | Gunicorn / Unicorn | uWSGI |
| Nginx → 后端 | HTTP(proxy_pass) | uwsgi 协议(uwsgi_pass,二进制更省) |
| 客户端 → Nginx | HTTP/HTTPS | HTTP/HTTPS |
| 能否直接对外 | 技术上可以,但不推荐 | 技术上可以,仍不推荐 |
# Nginx 配置片段(两种后端对照)
# Gunicorn(HTTP)
location / { proxy_pass http://127.0.0.1:8000; }
# uWSGI(自有协议,需安装 nginx uwsgi 模块)
location / { uwsgi_pass 127.0.0.1:3031; include uwsgi_params; }
一句话收尾:uWSGI、Gunicorn、Unicorn 都是「pre-fork 应用服务器」——master 管进程、worker 干活的并发模型;差异在语言、协议丰富度、以及并发维度(纯进程 / 加线程 / 加协程事件循环)。它们和上一页的 epoll 单线程是两条互补的扛并发路线。
怎么选?
🟩选 Gunicorn
Python + WSGI,要简单、好上手、社区大。IO 密集就上 gevent worker;默认 sync 足够多数场景。
🟦选 uWSGI
要极致性能/功能、需要线程+异步混合、或非 Python 应用、或想用 uwsgi 二进制协议省开销。
🟪选 Unicorn
你在写 Ruby/Rails。它是 Ruby 生态的默认 pre-fork 服务器,哲学极简。
常见误解
❌「Gunicorn 用 epoll 多路复用」
错。默认 sync worker 是阻塞 IO + 多进程,根本没用 epoll;只有 gevent/eventlet worker 才用事件循环(epoll)。
❌「Unicorn 是 Python 的」
错。Unicorn 是 Ruby 的服务器;Gunicorn(Green Unicorn)才是受它启发写的 Python 版。
❌「worker 越多性能越好」
错。进程有内存成本(每进程一份解释器+代码,COW 只能省一部分),过多反而因上下文切换和内存拖慢。
❌「应用服务器该直接对外」
错。生产应放 Nginx 后:TLS、静态、挡慢客户端、负载均衡都交给它。
记忆口诀:「pre-fork 是 master 生 worker,worker 阻塞各干各;并发看进程数,要更高就加线程或换协程(gevent=epoll 进 worker);uWSGI 全能、Gunicorn 省心、Unicorn 是 Ruby 亲戚;前面永远垫个 Nginx。」