一、Web 服务器为什么需要并发模型?
假设 Web Server:
192.168.1.100:80
同时有大量客户端访问:
Client A ──┐
Client B ──┤
Client C ──┤
Client D ──┼──→ Web Server
Client E ──┤
... │
Client N ──┘
服务器需要解决:
如何同时处理大量客户端连接?
最简单的服务器可以:
accept()
↓
处理 Client A
↓
完成
↓
accept()
↓
处理 Client B
这意味着:
A 没处理完
↓
B 只能等待
显然无法满足现代 Web 服务需求。
因此服务器需要:
Concurrency Model(并发模型)
二、经典 Web Server 并发模型
学习 Web Server 时,经常会看到:
Prefork
Worker
Event
但需要特别注意:
prefork、worker、event 这三个名字尤其常见于 Apache HTTP Server 的 MPM(Multi-Processing Module)模型。
而 Nginx 虽然同样使用:
Master Process
+
Worker Process
+
Event-Driven I/O
但不能简单把 Apache 的三个 MPM 名称直接套到 Nginx 上。
三、三种模型的核心思想
可以先建立最简单的认识:
Prefork
=
多进程
Worker
=
多进程 + 多线程
Event
=
多进程/线程 + 事件驱动 I/O
它们主要是在解决:
一个服务器
如何同时管理大量连接?
四、Prefork:多进程模型
Prefork:
Pre
+
Fork
核心思想:
主进程提前 fork 出多个子进程,由子进程负责处理客户端连接。
结构:
Master Process
│
┌─────────┼─────────┐
│ │ │
▼ ▼ ▼
Process 1 Process 2 Process 3
│ │ │
▼ ▼ ▼
Client A Client B Client C
可以简化理解:
一个 Process
≈
某一时刻处理一个 Connection / Request
具体行为仍取决于服务器实现、Keep-Alive 等机制。
五、为什么叫 Prefork?
因为服务器不是:
来一个请求
↓
fork()
才临时创建进程。
而通常会:
Server Start
↓
Master
↓
预先 fork()
↓
Child Process Pool
即:
Master
│
├── Child 1
├── Child 2
├── Child 3
├── Child 4
└── Child 5
客户端到来以后:
Existing Child
↓
处理连接
这样避免了每次请求都临时创建进程的巨大开销。
六、Prefork 的优点
进程之间具有较强隔离性:
Process A
Virtual Address Space
│
X
│
Process B
Virtual Address Space
因此:
一个 Worker Process 崩溃
通常不会直接破坏另一个 Worker Process 的用户空间。
优点:
模型简单
隔离性好
稳定
线程安全问题较少
适合某些非线程安全模块
七、Prefork 的缺点
每个进程都需要:
Virtual Address Space
Page Table
Process State
Kernel Resources
所以大量连接意味着可能需要大量进程:
100 Connections
↓
很多 Processes
进程数量增大以后:
Memory Consumption ↑
Context Switch ↑
Process Management Overhead ↑
因此 Prefork 对超高并发连接并不理想。
八、Worker:多进程 + 多线程模型
Worker 模型进一步引入:
Thread
结构更接近:
Master
│
┌──────────┴──────────┐
▼ ▼
Process 1 Process 2
│ │
┌─────┼─────┐ ┌─────┼─────┐
▼ ▼ ▼ ▼ ▼ ▼
Thread Thread Thread Thread Thread Thread
│ │ │ │ │ │
Client Client Client Client Client Client
所以:
Worker 不是“每个子进程只有一个线程”。
而通常是:
Master
↓
多个 Child Processes
↓
每个 Child Process
↓
多个 Worker Threads
九、Worker 为什么比 Prefork 更节省资源?
因为同一个进程中的线程共享:
Virtual Address Space
Code
Global Data
Heap
Open File Descriptors
...
但每个线程拥有自己的:
TID
Stack
Registers
Program Counter
Scheduling State
TLS
...
所以:
100 Processes
和:
10 Processes
×
10 Threads
虽然都可能提供大量并发执行单元,但资源模型完全不同。
线程通常比独立进程更加轻量。
十、Linux 中线程到底是什么?
Linux 内核并不像某些教材那样把:
Process
Thread
设计成完全不同的两个世界。
Linux 内核主要调度:
Task
用户空间可以通过:
pthread_create()
创建 POSIX Thread。
底层 libc/threading implementation 会使用 Linux 提供的线程创建机制,例如:
clone()
或现代相关接口。
核心思想是:
clone()
+
共享特定资源
从而形成:
多个 Task
共享同一个进程的主要资源
因此:
pthread_create()
是用户程序通常使用的 POSIX API;
而:
clone()
属于更底层的 Linux 机制。
十一、Worker 模型的问题
线程虽然比进程轻量,但如果采用:
One Thread
≈
One Connection
那么:
10,000 Connections
仍然可能意味着:
大量 Threads
而每个线程都有:
Stack
Scheduling State
Registers
Kernel Scheduling Cost
因此大量线程仍然会产生:
Memory Overhead
Context Switch
Scheduler Pressure
Lock Contention
于是出现了另一个重要方向:
Event-Driven Architecture
十二、Event:事件驱动模型
Event Model 的核心思想是:
一个线程/进程不再阻塞等待某一个连接,而是同时管理大量连接,只处理当前“已经准备好”的连接。
例如:
Connection A
Connection B
Connection C
Connection D
Connection E
│
│
▼
Event Poller
│
▼
哪些 Connection Ready?
│
├── A readable
├── C writable
└── E readable
│
▼
只处理 A/C/E
因此不需要:
Connection A
↓
Thread A
Connection B
↓
Thread B
Connection C
↓
Thread C
而可以:
Thread
│
└── Event Loop
│
├── Connection A
├── Connection B
├── Connection C
├── Connection D
├── Connection E
└── ...
十三、阻塞 I/O 模型的问题
假设:
read(fd, buf, size);
如果:
fd 没有数据
在阻塞模式下:
Thread
↓
read()
↓
Sleep / Block
这个线程暂时无法继续处理其他连接。
如果使用:
One Thread Per Connection
那么:
10,000 个空闲 Keep-Alive Connections
也可能占用大量线程资源。
这就是传统线程模型面对大量慢连接时的重要问题之一。
十四、事件驱动的核心思想
事件驱动模型:
不要让线程
↓
阻塞等待某一个 fd
而是:
告诉 Kernel:
“帮我观察这一批 fd”
↓
Kernel 观察它们
↓
哪个 fd Ready
↓
通知 Application
↓
Application 处理 Ready fd
结构:
Kernel
│
┌─────────┼─────────┐
│ │ │
fd 10 fd 11 fd 12
│ │ │
Ready Wait Ready
│ │
└────────┬──────────┘
▼
Event Loop
│
┌──────┴──────┐
▼ ▼
Handle 10 Handle 12
十五、Linux epoll
Linux 中非常重要的 I/O 事件通知机制:
epoll
常见 API:
epoll_create1()
epoll_ctl()
epoll_wait()
概念过程:
epoll_create1()
↓
创建 epoll instance
epoll_ctl()
↓
注册 / 修改 / 删除关注的 fd
epoll_wait()
↓
等待 Ready Events
↓
返回当前已经就绪的 fd
十六、epoll 工作模型
例如:
Socket fd=10
Socket fd=11
Socket fd=12
Socket fd=13
Socket fd=14
注册到:
epoll
以后:
Application
│
│ epoll_wait()
▼
Kernel
│
│
├── fd 10 → no event
├── fd 11 → readable
├── fd 12 → no event
├── fd 13 → readable
└── fd 14 → writable
│
▼
Application 得到:
11
13
14
于是程序只处理:
Ready File Descriptors
而不是遍历所有连接并对每个连接执行阻塞式等待。
十七、Event Loop
事件驱动程序通常存在:
Event Loop
概念代码:
while (1) {
events = epoll_wait(...);
for (each ready_event) {
if (new_connection)
accept(...);
else if (readable)
read(...);
else if (writable)
write(...);
}
}
于是:
Event Loop
↓
等待事件
↓
处理 Ready Connection
↓
返回 Event Loop
↓
等待下一批事件
十八、Event Model 的优势
最大的优势:
少量进程/线程可以管理大量连接。
例如:
10,000 Connections
并不意味着:
10,000 Processes
也不意味着:
10,000 Threads
而可能是:
少量 Worker Processes
↓
每个 Worker
↓
Event Loop
↓
管理大量 Connections
因此非常适合:
HTTP Keep-Alive
Reverse Proxy
Web Server
API Gateway
大量并发网络连接
十九、Event Driven 不等于“没有线程”
这是非常重要的一点。
事件驱动:
Event Driven
描述的是:
I/O 并发处理方式。
而不是:
“程序绝对不能有线程。”
一个服务器完全可以:
Multiple Processes
+
Multiple Threads
+
Event Loop
这些机制可以组合。
所以:
Process Model
Thread Model
Event Model
并不总是三个完全互斥的东西。
二十、Apache 的三种经典 MPM
Apache HTTP Server 中经典的:
prefork
worker
event
属于:
MPM
即:
Multi-Processing Module
可以简单比较:
Apache prefork
Parent
│
├── Child Process
├── Child Process
├── Child Process
└── Child Process
Apache worker
Parent
│
├── Child Process
│ ├── Thread
│ ├── Thread
│ └── Thread
│
└── Child Process
├── Thread
├── Thread
└── Thread
Apache event
Parent
│
├── Child Process
│ ├── Threads
│ └── Event-oriented connection handling
│
└── Child Process
├── Threads
└── Event-oriented connection handling
所以:
prefork
worker
event
这三个术语尤其应该放到:
Apache MPM
体系中理解。
二十一、Nginx 的工作模型
Nginx 的经典架构主要是:
Master Process
│
├── Worker Process 1
│ └── Event Loop
│
├── Worker Process 2
│ └── Event Loop
│
├── Worker Process 3
│ └── Event Loop
│
└── Worker Process N
└── Event Loop
因此:
Nginx 本身就是典型的多进程 + 事件驱动架构。
不能简单描述为:
Nginx prefork mode
Nginx worker mode
Nginx event mode
这会和 Apache MPM 混淆。
二十二、Nginx Master Process
Nginx 的:
Master Process
主要负责:
读取 / 验证配置
管理 Worker
启动 Worker
重新加载配置
处理管理信号
平滑升级等
Master 通常不负责直接处理普通客户端 HTTP 请求数据。
二十三、Nginx Worker Process
真正处理大量客户端连接的主要是:
Worker Process
每个 Worker:
Worker Process
│
▼
Event Loop
│
▼
epoll
│
├── Client 1
├── Client 2
├── Client 3
├── Client 4
└── ...
因此一个 Worker:
可以同时管理大量连接。
不是:
一个 Worker
=
一个 Connection
二十四、Nginx 与 epoll
Linux 上 Nginx 可以利用:
epoll
实现高效事件通知。
可以概念化:
Nginx Worker
│
▼
Event Loop
│
▼
epoll_wait()
│
▼
Kernel
│
┌───────────────┼───────────────┐
│ │ │
Socket A Socket B Socket C
readable waiting writable
│ │
└───────────────┬───────────────┘
▼
Ready Events
│
▼
Nginx Worker
二十五、Nginx 为什么适合大量连接?
因为它避免为每个连接创建:
一个 Process
或者:
一个 Thread
经典 Nginx Worker 更接近:
一个 Worker Process
↓
一个核心 Event Loop
↓
管理大量 Connections
因此:
Connection Count ↑
并不要求:
Process Count ↑ 同样数量
或者:
Thread Count ↑ 同样数量
这使它特别适合:
Reverse Proxy
HTTP Server
Load Balancer
大量 Keep-Alive Connections
二十六、worker_processes
Nginx 配置:
worker_processes auto;
表示:
让 Nginx 自动确定合适的 Worker Process 数量。
在常见环境中,它通常会依据:
可用 CPU 数量
进行设置。
所以经常可以近似理解:
CPU 4 个可用逻辑 CPU
↓
大约 4 个 Worker Processes
但更准确的理解不是:
“物理 CPU 一个核心绝对对应一个 Worker。”
因为现代 CPU 还有:
Physical CPU
Core
Logical CPU
SMT / Hyper-Threading
CPU Affinity
Container CPU Limit
等概念。
所以:
worker_processes auto;
比简单写:
一个物理核心 = 一个 Worker
更加准确。
二十七、为什么常让 Worker 数量接近 CPU 数量?
假设:
4 Logical CPUs
运行:
4 Worker Processes
可以让:
Worker 1 → CPU 0
Worker 2 → CPU 1
Worker 3 → CPU 2
Worker 4 → CPU 3
并行处理事件。
如果创建:
1000 Worker Processes
而只有:
4 CPUs
大量 Worker 之间会竞争 CPU:
Context Switch ↑
Scheduler Overhead ↑
Cache Locality ↓
所以:
Worker 并不是越多越好。
二十八、worker_connections
配置:
events {
worker_connections 1024;
}
你原笔记写:
每个 worker 最多打开 1024 个文件,处理 1024 个请求。
这两个说法都不够准确。
worker_connections 更准确表示:
单个 Worker Process 能够同时打开/管理的连接数上限配置。
重点是:
Connections
不是:
Requests
二十九、Connection 和 Request 不一样
这是 Web Server 中非常重要的概念。
例如 HTTP/1.1 Keep-Alive:
TCP Connection
│
├── HTTP Request 1
├── HTTP Response 1
│
├── HTTP Request 2
├── HTTP Response 2
│
├── HTTP Request 3
└── HTTP Response 3
所以:
1 TCP Connection
可以处理:
多个 HTTP Requests
因此:
worker_connections 1024
绝对不能简单理解成:
一个 Worker 一共只能处理 1024 个 HTTP 请求
它可以在生命周期中处理远远超过这个数量的请求。
三十、理论连接数估算
假设:
worker_processes 4;
events {
worker_connections 1024;
}
粗略理论上限可以先理解:
4 × 1024
=
4096 Connections
但是:
这只是非常粗略的上限估算。
实际还受到:
File Descriptor Limit
System Resource Limit
Upstream Connections
Memory
CPU
Socket Buffer
Kernel Parameters
Nginx Configuration
Workload
等限制。
三十一、反向代理时不能简单按 4 × 1024 算客户端
例如:
Client
│
▼
Nginx
│
▼
Tomcat
Nginx 可能同时维护:
Client Connection
+
Upstream Connection
即一个请求可能涉及:
Client Socket
+
Backend Socket
所以:
worker_connections
不仅仅用于:
Client Connections
还可能包含 Nginx 与:
Upstream Server
之间的连接。
因此反向代理场景中:
worker_processes
×
worker_connections
不能直接等价于:
最大客户端数量
三十二、File Descriptor Limit
因为 Socket 在 Linux 中也通过:
File Descriptor
访问,所以连接数量还受到:
FD Limit
影响。
例如:
ulimit -n
查看当前 shell 的:
Open File Limit
Nginx 也可以涉及:
worker_rlimit_nofile
等配置。
因此:
worker_connections 100000;
并不意味着系统自动就能真正建立:
100000 Connections
还必须检查:
Process FD Limit
Kernel Limits
Memory
Network Configuration
等条件。
三十三、Socket 为什么也是 FD?
这正好可以连接前面的“进程”章节。
Nginx Worker:
Worker Process
│
▼
FD Table
│
├── fd 0
├── fd 1
├── fd 2
├── fd 10 → Client Socket
├── fd 11 → Client Socket
├── fd 12 → Upstream Socket
├── fd 13 → Client Socket
└── ...
然后:
epoll
监视这些:
File Descriptors
所以前面学习的:
Process
→ FD Table
和这里:
Socket
→ epoll
终于连接起来了。
三十四、从 TCP 到 Nginx
结合 TCP 三次握手,可以形成完整模型:
Client
│
│ TCP SYN
▼
Linux Kernel
│
│ TCP Handshake
▼
Listening Socket
│
│ New Connection
▼
Nginx Worker
│
│ accept()
▼
Connected Socket FD
│
│ epoll
▼
Event Loop
│
│ readable
▼
read()
│
▼
HTTP Request
│
▼
Nginx HTTP Processing
│
▼
HTTP Response
│
▼
write()
│
▼
TCP
│
▼
Client
这就是:
TCP → Socket → FD → epoll → Nginx → HTTP
完整的一条链。
三十五、慢连接为什么适合 Event Model?
假设有:
10,000 Clients
但其中:
9,900
当前没有数据发送。
传统“一连接一线程”模型可能仍然维护大量线程。
而 Event Model:
10,000 Connections
↓
epoll
↓
只有 100 个 Ready
↓
Worker 只处理这 100 个
所以:
事件驱动模型特别适合大量连接中只有少量连接在某一时刻真正活跃的场景。
这也是 Web Server、Proxy、Gateway 常用事件驱动架构的重要原因。
三十六、Event Model 的限制
Event Driven 并不是没有缺点。
最重要的问题之一:
Event Loop 不能被耗时操作长时间阻塞。
例如一个 Worker:
Event Loop
↓
处理 Client A
↓
执行一个耗时 10 秒的阻塞操作
↓
整个 Worker 无法及时处理其他 Ready Events
那么:
Client B
Client C
Client D
即使已经 Ready,也可能等待。
因此事件驱动程序非常强调:
Non-blocking I/O
Asynchronous Processing
快速返回 Event Loop
三十七、CPU 密集任务也会阻塞 Event Loop
例如:
Nginx Worker
↓
执行巨大 CPU 计算
↓
占用 CPU 10 秒
即使没有阻塞 I/O:
Event Loop
同样无法及时处理其他连接。
所以:
Event Driven 解决的是高并发 I/O 管理问题,不代表一个线程拥有无限计算能力。
三十八、Web 应用的真实架构通常不止 Web Server
现实中经常是:
Internet
│
▼
Nginx
│
├── Static Files
│
└── Reverse Proxy
│
▼
Application Server
│
┌─────┼─────┐
▼ ▼ ▼
Worker Worker Worker
│
▼
Database
例如:
Browser
↓
Nginx
↓
Gunicorn / PHP-FPM / Tomcat / Node.js
↓
MySQL / PostgreSQL / Redis
所以:
Nginx 的事件模型和后端应用服务器的并发模型可以完全不同。
三十九、典型组合
例如:
Nginx
=
Master
+
Workers
+
epoll Event Loop
后面连接:
Application Server
=
Process Pool
+
Thread Pool
数据库:
Database
=
Processes / Threads
+
Buffer Pool
+
I/O
最终形成:
Client
↓
TCP
↓
Nginx Event Driven
↓
Reverse Proxy
↓
Application Worker
↓
Database
四十、Prefork、Worker、Event 对比
| 模型 | 核心结构 | 并发单位 | 资源消耗 | 大量连接能力 | 主要思想 |
|---|---|---|---|---|---|
| Prefork | 多进程 | Process | 较高 | 相对较弱 | 进程隔离 |
| Worker | 多进程 + 多线程 | Thread | 中等 | 较好 | 线程并发 |
| Event | Event Loop + I/O Multiplexing | Event / Connection | 较低 | 很强 | 就绪事件驱动 |
但要注意:
Event 可以与多进程、多线程组合,因此它并不是与 Process/Thread 完全平级且互斥的概念。
四十一、三个模型最直观的区别
Prefork
Connection 1 → Process 1
Connection 2 → Process 2
Connection 3 → Process 3
核心:
多进程
Worker
Process 1
├── Thread 1 → Connection
├── Thread 2 → Connection
└── Thread 3 → Connection
Process 2
├── Thread 1 → Connection
├── Thread 2 → Connection
└── Thread 3 → Connection
核心:
多进程
+
多线程
Event
Worker
│
▼
Event Loop
│
├── Connection 1
├── Connection 2
├── Connection 3
├── Connection 4
├── Connection 5
├── ...
└── Connection N
核心:
一个执行单元
管理大量连接
只处理 Ready Event
四十二、select、poll、epoll
Linux I/O 多路复用还会遇到:
select
poll
epoll
可以先建立简单认识:
select
↓
较早的 I/O Multiplexing API
poll
↓
改进一些 select 的接口限制
epoll
↓
Linux 专有的高扩展性事件通知接口
Nginx 在 Linux 上:
epoll
是非常重要的底层机制。
以后可以单独深入:
Level Triggered
vs
Edge Triggered
四十三、Nginx 配置的正确理解
典型:
worker_processes auto;
events {
worker_connections 1024;
}
应该理解成:
worker_processes auto
│
▼
根据运行环境自动确定 Worker Process 数量
worker_connections 1024
│
▼
每个 Worker 最多管理一定数量的 Connections
而不是:
一个 CPU Core
只能处理一个 Worker Connection
也不是:
一个 Worker
只能处理 1024 个 HTTP Requests
四十四、Nginx 高并发整体模型
最终可以画成:
Nginx Master
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
Worker 1 Worker 2 Worker 3
│ │ │
▼ ▼ ▼
Event Loop Event Loop Event Loop
│ │ │
▼ ▼ ▼
epoll epoll epoll
│ │ │
┌───┼───┐ ┌───┼───┐ ┌───┼───┐
│ │ │ │ │ │ │ │ │
fd fd fd fd fd fd fd fd fd
│ │ │ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
Client Connections Connections Connections
其中:
Master
→ 管理 Workers
Worker
→ 真正处理网络连接
epoll
→ Kernel 提供 I/O Event Notification
Event Loop
→ 根据 Ready Events 调度连接处理
Socket FD
→ 表示具体网络连接
四十五、和前面知识串起来
现在可以把之前学习的:
进程
线程
文件描述符
Signal
TCP
全部串起来。
完整过程:
Nginx Master Process
│
│ fork()
▼
Worker Processes
│
│
▼
epoll
│
│ monitor
▼
Socket FDs
│
▼
TCP Connections
│
▼
HTTP Requests
│
▼
Application Logic
管理方面:
Signal
↓
Master
↓
Reload / Stop / Worker Management
网络方面:
TCP Three-Way Handshake
↓
Connection
↓
Socket FD
↓
epoll
↓
Nginx Worker
进程方面:
Master
↓
fork()
↓
Worker Process
因此这些并不是孤立的知识点,而是一套完整的 Linux Server 工作模型。
四十六、最终记忆模型
Prefork
Master
↓
fork()
↓
Many Processes
↓
Processes handle connections
关键词:
Process
Isolation
High Memory Cost
Worker
Master
↓
Child Processes
↓
Many Threads
↓
Threads handle concurrent work
关键词:
Process
+
Thread
Event
Worker
↓
Event Loop
↓
epoll
↓
Many Socket FDs
↓
Ready Events
关键词:
Non-blocking I/O
I/O Multiplexing
Event Driven
High Concurrency
Nginx
Master
↓
Worker Processes
↓
Event Loop
↓
epoll
↓
Thousands of Connections
关键词:
Master-Worker
+
Event Driven
+
Non-blocking I/O
四十七、一句话总结
Web Server 的并发模型解决的是“如何用有限的 CPU、内存和执行单元同时处理大量客户端连接”。Prefork 主要依靠多个进程实现并发,隔离性强但进程资源开销较大;Worker 模型通常采用多个进程、每个进程多个线程,提高资源利用率;Event 模型则利用非阻塞 I/O 和 epoll 等 I/O 多路复用/事件通知机制,使少量进程或线程能够同时管理大量连接。Apache 的 prefork、worker、event 是经典 MPM 模型,而 Nginx 采用 Master + Worker Processes + Event Loop 的架构,在 Linux 上通常利用 epoll 管理大量 Socket FD。
worker_processes auto用于自动确定 Worker 数量,worker_connections表示单个 Worker 可管理的连接数上限配置,而不是该 Worker 一共只能处理多少 HTTP 请求。
浙公网安备 33010602011771号