Nginx 是全球使用最广泛的反向代理和 Web 服务器。截至 2024
年,它服务了互联网上超过 34% 的活跃网站。但大多数工程师对
Nginx 的理解停留在配置文件层面——知道怎么配
location 和
upstream,但不知道一个请求进入 Nginx
后经历了什么。
这篇文章从架构层面剖析 Nginx:它为什么快?Master/Worker 模型的设计动机是什么?epoll 在其中扮演什么角色?Upstream 连接池怎么管理?理解这些才能在生产环境中正确调优 Nginx。
一、Master-Worker 进程模型
1.1 进程架构
Nginx 采用经典的 Master-Worker 多进程模型:
┌──────────────────┐
│ Master Process │
│ (PID 1) │
│ - 读取配置 │
│ - 管理 Worker │
│ - 绑定端口 │
│ - 信号处理 │
└────────┬─────────┘
│ fork()
┌──────────────┼──────────────┐
│ │ │
┌────────┴──────┐ ┌────┴──────┐ ┌─────┴───────┐
│ Worker #0 │ │ Worker #1 │ │ Worker #2 │
│ - epoll │ │ - epoll │ │ - epoll │
│ - 处理请求 │ │ - 处理请求 │ │ - 处理请求 │
│ - 独立事件循环│ │ - 独立事件 │ │ - 独立事件 │
└───────────────┘ └───────────┘ └─────────────┘
│ │ │
└──────────────┴──────────────┘
共享 listen socket
Master 进程的职责:
- 读取并验证配置文件
- 创建、绑定、关闭 listen socket
- 启动、终止、维护 Worker 进程
- 处理信号(HUP/QUIT/TERM/USR1/USR2)
- 执行热升级(binary upgrade)
Worker 进程的职责:
- 接受新连接(accept)
- 读取请求、处理请求、发送响应
- 与上游服务器通信(proxy_pass)
- 缓存管理
- 日志写入
# 查看 Nginx 进程结构
ps aux --forest | grep nginx
# root 1234 0.0 0.1 master process /usr/sbin/nginx
# www-data 1235 0.5 0.8 \_ worker process
# www-data 1236 0.5 0.8 \_ worker process
# www-data 1237 0.5 0.8 \_ worker process
# www-data 1238 0.5 0.8 \_ worker process
# Worker 数量通常设置为 CPU 核心数
cat /proc/cpuinfo | grep processor | wc -l
# 4
# nginx.conf
worker_processes auto; # 自动设置为 CPU 核心数
# 等价于: worker_processes 4;1.2 Worker 的 CPU 亲和
每个 Worker 进程可以绑定到特定的 CPU 核心,避免上下文切换和缓存失效:
# 手动绑定
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;
# Worker 0 → CPU 0, Worker 1 → CPU 1, ...
# 自动绑定(推荐)
worker_processes auto;
worker_cpu_affinity auto;
# 验证 CPU 亲和是否生效
for pid in $(pgrep -P $(cat /run/nginx.pid)); do
echo "Worker PID $pid: CPU $(taskset -p $pid | awk '{print $NF}')"
done
# Worker PID 1235: CPU 1
# Worker PID 1236: CPU 2
# Worker PID 1237: CPU 4
# Worker PID 1238: CPU 81.3 信号与热更新
Nginx 使用 Unix 信号进行进程间通信:
| 信号 | 效果 | 命令 |
|---|---|---|
| HUP | 重新加载配置 | nginx -s reload |
| QUIT | 优雅关闭(处理完当前请求) | nginx -s quit |
| TERM/INT | 立即关闭 | nginx -s stop |
| USR1 | 重新打开日志文件 | nginx -s reopen |
| USR2 | 热升级二进制文件 | kill -USR2 $(cat /run/nginx.pid) |
# reload 的内部过程
nginx -s reload
# 1. Master 读取新配置并验证语法
# 2. Master fork 新的 Worker 进程(使用新配置)
# 3. Master 向旧 Worker 发送 QUIT 信号
# 4. 旧 Worker 停止接受新连接
# 5. 旧 Worker 处理完当前请求后退出
# 6. 整个过程不中断任何连接
# 验证 reload 过程
watch -n 0.5 'ps aux | grep "nginx: worker"'
# 可以看到新旧 Worker 共存的短暂窗口热升级(Binary Upgrade)的完整流程:
# Step 1: 编译新版本 Nginx(不停止旧版本)
./configure --prefix=/usr/local/nginx --with-...
make
# 不执行 make install
# Step 2: 备份旧二进制
cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old
# Step 3: 替换二进制(旧进程仍在运行)
cp objs/nginx /usr/local/nginx/sbin/nginx
# Step 4: 向旧 Master 发送 USR2
kill -USR2 $(cat /run/nginx.pid)
# 旧 Master 将 PID 文件重命名为 nginx.pid.oldbin
# 旧 Master 启动新 Master(使用新二进制)
# 新 Master 启动新 Worker
# Step 5: 此时新旧 Master 和 Worker 同时运行
ps aux | grep nginx
# 旧 Master + 旧 Worker + 新 Master + 新 Worker
# Step 6: 向旧 Master 发送 QUIT,优雅关闭旧 Worker
kill -QUIT $(cat /run/nginx.pid.oldbin)
# Step 7: 如果新版本有问题,回滚
kill -HUP $(cat /run/nginx.pid.oldbin) # 恢复旧 Master 的 Worker
kill -QUIT $(cat /run/nginx.pid) # 关闭新 Master二、事件驱动与 epoll
2.1 为什么用事件驱动
传统的多进程/多线程模型(如 Apache prefork)为每个连接分配一个进程或线程:
Apache prefork 模型:
连接 1 → 进程 1 (占用 2-10MB 内存)
连接 2 → 进程 2 (占用 2-10MB 内存)
...
连接 1000 → 进程 1000 (占用 2-10GB 内存)
问题: 10000 个并发连接 = 20-100GB 内存 + 大量上下文切换
Nginx 事件驱动模型:
Worker 0 → 同时处理 5000 个连接 (事件循环)
Worker 1 → 同时处理 5000 个连接 (事件循环)
优势: 10000 个并发连接 ≈ 几百 MB 内存,几乎无上下文切换
Nginx 的 Worker 进程运行一个事件循环(Event Loop),使用 epoll(Linux)/ kqueue(FreeBSD)/ IOCP(Windows)等多路复用机制,在单线程内高效处理数千个并发连接。
2.2 事件循环的工作流程
Worker 事件循环:
while (true) {
// 1. 调用 epoll_wait,阻塞等待事件
events = epoll_wait(epfd, events, max_events, timeout);
// 2. 处理定时器事件(超时连接、keepalive 超时等)
process_timers();
// 3. 遍历就绪事件
for (event in events) {
if (event.is_accept) {
// 新连接到达
new_fd = accept(listen_fd);
epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, ...);
}
else if (event.is_readable) {
// 可读:读取客户端请求或上游响应
read_handler(event.connection);
}
else if (event.is_writable) {
// 可写:发送响应给客户端或请求给上游
write_handler(event.connection);
}
}
}
关键设计:单个 Worker 进程中所有操作都是非阻塞的。读取文件、发送网络数据、连接上游——任何操作如果不能立即完成,Worker 不会等待,而是注册一个事件回调,继续处理其他连接。当数据准备好时,epoll 会通知 Worker。
2.3 epoll 配置
events {
worker_connections 65535; # 每个 Worker 最大连接数
use epoll; # Linux 使用 epoll(通常自动选择)
multi_accept on; # Worker 一次 accept 多个连接
accept_mutex off; # 关闭 accept 互斥锁(Linux 3.9+ 推荐)
}
| 参数 | 默认值 | 推荐值 | 说明 |
|---|---|---|---|
| worker_connections | 512 | 65535 | 每 Worker 最大连接数(含上游连接) |
| multi_accept | off | on | 一次系统调用 accept 多个连接 |
| accept_mutex | on | off | Linux 3.9+ 有 EPOLLEXCLUSIVE,不需要互斥 |
# 验证 epoll 是否生效
strace -e epoll_wait -p $(pgrep -f "nginx: worker" | head -1) 2>&1 | head
# epoll_wait(8, [{EPOLLIN, {u32=...}}], 512, 10000) = 1
# 看到 epoll_wait 调用即确认使用了 epoll2.4 惊群问题(Thundering Herd)
当多个 Worker 同时监听同一个 listen socket 时,新连接到达会唤醒所有 Worker,但只有一个能成功 accept——其他 Worker 白白唤醒:
新连接到达 listen socket
→ 唤醒 Worker 0 (accept 成功 ✓)
→ 唤醒 Worker 1 (accept 失败,EAGAIN ✗)
→ 唤醒 Worker 2 (accept 失败,EAGAIN ✗)
→ 唤醒 Worker 3 (accept 失败,EAGAIN ✗)
Nginx 的解决方案演进:
- accept_mutex(早期):Worker 竞争互斥锁,拿到锁的才能 accept。减少惊群,但引入锁竞争。
- EPOLLEXCLUSIVE(Linux 4.5+):内核级解决,只唤醒一个 Worker。
- SO_REUSEPORT(Linux 3.9+):每个 Worker 有独立的 listen socket,内核自动分发连接。
# SO_REUSEPORT 配置(推荐)
server {
listen 80 reuseport; # 每个 Worker 独立的 listen socket
listen 443 ssl reuseport;
}
# 验证 reuseport 生效
ss -tlnp | grep ':80'
# LISTEN 0 511 *:80 *:* users:(("nginx",pid=1235,fd=6))
# LISTEN 0 511 *:80 *:* users:(("nginx",pid=1236,fd=6))
# LISTEN 0 511 *:80 *:* users:(("nginx",pid=1237,fd=6))
# 每个 Worker 有自己的 listen socketSO_REUSEPORT 的性能提升:Cloudflare 的测试显示,开启 reuseport 后,请求延迟的 P99 降低了 3 倍。因为不再有 accept 锁竞争,且内核的连接分发更均匀。
三、请求处理的完整路径
3.1 请求的 11 个阶段
Nginx 将 HTTP 请求处理分为 11 个阶段(Phase),模块可以挂在不同阶段执行:
POST_READ ← 读取请求头后(realip 模块)
SERVER_REWRITE ← server 块的 rewrite
FIND_CONFIG ← 匹配 location(内部)
REWRITE ← location 块的 rewrite
POST_REWRITE ← 重写后检查(内部)
PREACCESS ← 访问控制预处理(limit_conn, limit_req)
ACCESS ← 访问控制(auth_basic, auth_request)
POST_ACCESS ← 访问控制后处理(内部)
PRECONTENT ← 内容生成前(try_files, mirror)
CONTENT ← 内容生成(proxy_pass, fastcgi, static files)
LOG ← 日志记录(access_log)
# 一个典型配置中各阶段模块的分布
server {
listen 80;
server_name app.example.com;
# POST_READ: 获取真实 IP
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
# SERVER_REWRITE: server 级重写
rewrite ^/old-api/(.*) /api/v2/$1 permanent;
location /api/ {
# REWRITE: location 级重写
rewrite ^/api/v1/(.*) /api/v2/$1 break;
# PREACCESS: 限流
limit_req zone=api burst=20 nodelay;
limit_conn conn_zone 10;
# ACCESS: 认证
auth_request /auth;
# CONTENT: 代理到上游
proxy_pass http://backend;
}
# LOG: 日志
access_log /var/log/nginx/access.log main;
}
3.2 一个请求的完整生命周期
客户端 → TCP SYN → Nginx (listen socket)
│
├── accept(): 创建连接对象(ngx_connection_t)
├── 分配内存池(connection pool)
├── 添加读事件到 epoll
│
├── [等待可读]
│
├── read(): 读取请求行和请求头
├── 解析 HTTP 请求(状态机解析器)
├── 创建请求对象(ngx_http_request_t)
├── 匹配 server_name → 选择 server 块
├── 匹配 location → 选择 location 块
│
├── 执行 11 阶段处理链
│ ├── rewrite / access / rate-limit...
│ └── content: proxy_pass
│
├── [代理模式] 连接上游:
│ ├── 从连接池获取或新建上游连接
│ ├── 发送请求到上游
│ ├── 读取上游响应头
│ ├── 转发响应头给客户端
│ ├── 流式转发响应体
│ └── 归还上游连接到连接池
│
├── 写入访问日志
│
├── [Keep-Alive] 保持连接,等待下一个请求
└── [Close] 释放内存池,关闭 socket
3.3 内存池设计
Nginx 为每个连接和请求分配独立的内存池(Memory Pool),避免频繁的 malloc/free 系统调用:
连接内存池 (connection_pool_size):
├── 连接对象 (ngx_connection_t)
├── 读/写缓冲区
└── 生命周期: 连接建立 → 连接关闭
请求内存池 (request_pool_size):
├── 请求对象 (ngx_http_request_t)
├── 请求头解析结果
├── 变量值
└── 生命周期: 请求开始 → 请求结束
# 内存池大小配置
http {
connection_pool_size 512; # 连接内存池初始大小
request_pool_size 4k; # 请求内存池初始大小
client_header_buffer_size 1k; # 请求头缓冲区
large_client_header_buffers 4 8k; # 大请求头缓冲区
}
# 监控 Nginx 内存使用
ps -o pid,rss,vsz,comm -C nginx
# PID RSS VSZ COMMAND
# 1234 3456 5678 nginx: master
# 1235 45678 67890 nginx: worker ← 关注 RSS
# 如果 Worker RSS 持续增长,可能存在内存泄漏
# 使用 nginx -V 确认编译了 debug 模块后:
# error_log /var/log/nginx/debug.log debug;四、Upstream 与连接管理
4.1 Upstream 配置模型
Upstream 定义了一组后端服务器及其负载均衡策略:
upstream app_backend {
# 负载均衡算法
# 默认: 加权轮询 (Weighted Round Robin)
# least_conn; # 最少连接
# ip_hash; # 源 IP 哈希
# hash $request_uri consistent; # 一致性哈希
# 后端服务器
server 10.0.1.1:8080 weight=5 max_conns=100 max_fails=3 fail_timeout=30s;
server 10.0.1.2:8080 weight=3 max_conns=100 max_fails=3 fail_timeout=30s;
server 10.0.1.3:8080 weight=2 max_conns=100 max_fails=3 fail_timeout=30s;
# 备用服务器(所有主服务器不可用时启用)
server 10.0.1.100:8080 backup;
# Keepalive 连接池
keepalive 32; # 每个 Worker 保持 32 个空闲长连接
keepalive_time 1h; # 连接最大存活时间
keepalive_timeout 60s; # 空闲超时
keepalive_requests 1000; # 单个连接最大请求数
}
server {
location / {
proxy_pass http://app_backend;
# 必须配合 Keepalive 的关键参数
proxy_http_version 1.1;
proxy_set_header Connection ""; # 清除 Connection: close
}
}
4.2 Keepalive 连接池
Upstream Keepalive 是 Nginx 性能优化的关键——复用已建立的 TCP 连接,避免每次请求都三次握手:
没有 Keepalive:
请求 1: TCP 握手 → 发送 → 接收 → TCP 关闭
请求 2: TCP 握手 → 发送 → 接收 → TCP 关闭 ← 每次都要握手
请求 3: TCP 握手 → 发送 → 接收 → TCP 关闭
有 Keepalive:
请求 1: TCP 握手 → 发送 → 接收
请求 2: 发送 → 接收 ← 复用连接
请求 3: 发送 → 接收 ← 复用连接
...
空闲超时 → TCP 关闭
# 验证 Keepalive 是否生效
# 在 Nginx 机器上观察与后端的连接状态
ss -tnp | grep '10.0.1.1:8080'
# ESTAB 0 0 10.0.0.1:52134 10.0.1.1:8080 users:(("nginx",pid=1235,fd=12))
# ESTAB 0 0 10.0.0.1:52135 10.0.1.1:8080 users:(("nginx",pid=1235,fd=13))
# 看到 ESTABLISHED 状态的连接 = Keepalive 连接池中的连接
# 如果看到大量 TIME_WAIT,说明 Keepalive 没有生效
ss -tn state time-wait | grep '10.0.1.1:8080' | wc -l
# 如果数量很大,检查:
# 1. proxy_http_version 是否设置为 1.1
# 2. proxy_set_header Connection 是否清除了
# 3. 后端是否支持 Keep-AliveKeepalive 参数计算:
keepalive 数量 = (峰值 QPS / Worker 数) × 平均响应时间 × 1.5
例如:
峰值 QPS: 10000
Worker 数: 4
平均响应时间: 50ms
每 Worker QPS: 2500
每 Worker 并发连接: 2500 × 0.05 = 125
keepalive = 125 × 1.5 ≈ 192
配置: keepalive 192;
4.3 上游健康检查
Nginx 开源版只支持被动健康检查(请求失败后标记不健康)。主动健康检查需要 Nginx Plus 或第三方模块:
# 被动健康检查(开源版)
upstream backend {
server 10.0.1.1:8080 max_fails=3 fail_timeout=30s;
# max_fails=3: 30 秒内失败 3 次,标记为不健康
# fail_timeout=30s: 30 秒后重新尝试
}
# 主动健康检查(Nginx Plus)
upstream backend {
zone backend_zone 64k; # 共享内存(Worker 间共享状态)
server 10.0.1.1:8080;
server 10.0.1.2:8080;
}
server {
location / {
proxy_pass http://backend;
health_check interval=5s fails=3 passes=2 uri=/health;
# interval=5s: 每 5 秒检查一次
# fails=3: 连续失败 3 次标记不健康
# passes=2: 连续成功 2 次标记恢复
}
}
# 使用 nginx_upstream_check_module(第三方模块,用于开源版)
# 需要编译时添加模块
upstream backend {
server 10.0.1.1:8080;
server 10.0.1.2:8080;
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
# 查看健康状态
# curl http://localhost/upstream_status4.4 重试与超时
location / {
proxy_pass http://backend;
# 连接超时
proxy_connect_timeout 5s; # 连接上游的超时(TCP 握手)
proxy_send_timeout 60s; # 发送请求到上游的超时
proxy_read_timeout 60s; # 读取上游响应的超时
# 重试策略
proxy_next_upstream error timeout http_502 http_503;
# 在以下情况下尝试下一个上游:
# error: 连接错误
# timeout: 超时
# http_502: 上游返回 502
# http_503: 上游返回 503
proxy_next_upstream_tries 2; # 最多重试 2 次
proxy_next_upstream_timeout 10s; # 重试的总超时
}
重试的危险:
场景: POST 请求的重试
Client → Nginx → app1 (处理超时)
↓
Nginx 认为超时,重试到 app2
↓
app2 再次处理 POST 请求
↓
结果: 同一个订单被提交两次!
解决方案:
1. proxy_next_upstream 不包含非幂等方法
proxy_next_upstream error timeout;
# 不加 http_502/503 对 POST 请求
2. 或者使用 non_idempotent 标志
proxy_next_upstream error timeout non_idempotent;
# 只在显式声明时重试非幂等请求
3. 更好的方案: 后端实现幂等性(幂等 key)
五、缓冲区与代理行为
5.1 Proxy Buffer
Nginx 在代理模式下使用缓冲区暂存上游响应,这个设计至关重要:
无缓冲:
Client ←(慢)── Nginx ←(快)── Backend
Backend 连接被慢客户端占用,直到客户端接收完毕
Backend 的连接资源被浪费
有缓冲:
Client ←(慢)── Nginx ←(快)── Backend
Nginx 快速读取 Backend 响应到内存/磁盘缓冲
释放 Backend 连接
Nginx 按客户端速度慢慢发送
效果: Backend 连接被快速释放,提高 Backend 并发能力
location / {
proxy_pass http://backend;
# 缓冲开关
proxy_buffering on; # 开启缓冲(默认)
# 响应头缓冲
proxy_buffer_size 4k; # 响应头缓冲区(第一个 buffer)
# 响应体缓冲
proxy_buffers 8 16k; # 8 个 16k 的缓冲区
proxy_busy_buffers_size 32k; # 正在发送的缓冲区最大值
# 磁盘缓冲(内存不够时写磁盘)
proxy_max_temp_file_size 1024m; # 临时文件最大值
proxy_temp_file_write_size 32k; # 每次写磁盘的大小
proxy_temp_path /var/cache/nginx/temp; # 临时文件目录
}
什么时候关闭缓冲:
# SSE (Server-Sent Events) / 流式响应
location /events {
proxy_pass http://backend;
proxy_buffering off; # 关闭缓冲,实时转发
proxy_cache off; # 关闭缓存
proxy_read_timeout 3600s; # 长连接超时设长
}
# WebSocket
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
proxy_read_timeout 3600s;
}
5.2 客户端请求体缓冲
大文件上传时,Nginx 的请求体缓冲行为也需要注意:
http {
# 客户端请求体
client_max_body_size 100m; # 最大请求体(超过返回 413)
client_body_buffer_size 16k; # 请求体内存缓冲区
client_body_temp_path /var/cache/nginx/client_temp;
# 如果请求体 > client_body_buffer_size:
# 1. 写入 client_body_temp_path 的临时文件
# 2. 然后再转发给上游
# 这意味着: 上传 100MB 文件 → 先写 100MB 到 Nginx 磁盘 → 再传给后端
# 延迟翻倍!
# 关闭请求体缓冲(直接透传给上游)
# proxy_request_buffering off; # 适用于大文件上传场景
}
六、共享内存与 Worker 间通信
6.1 共享内存区域
Worker 进程是独立的——它们不共享内存。但某些功能需要跨 Worker 协调(如限流、健康检查状态)。Nginx 使用共享内存(Shared Memory Zone)解决这个问题:
# 限流的共享内存
http {
# 创建名为 "req_limit" 的共享内存区,大小 10MB
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=100r/s;
# $binary_remote_addr 占 16 字节/IP
# 10MB ≈ 160,000 个 IP 的限流状态
limit_conn_zone $binary_remote_addr zone=conn_limit:5m;
server {
location /api/ {
limit_req zone=req_limit burst=20 nodelay;
limit_conn conn_limit 10;
}
}
}
# Upstream 的共享内存(Nginx Plus)
upstream backend {
zone backend_zone 64k; # Worker 间共享后端状态
server 10.0.1.1:8080;
server 10.0.1.2:8080;
}
# 查看共享内存使用情况
# 如果使用了 Nginx Plus 或有 status 模块
curl http://localhost/api/status/shared_zones
# 开源版可以通过 /proc 查看
cat /proc/$(cat /run/nginx.pid)/maps | grep -i nginx6.2 限流的精确性
因为使用共享内存,Nginx 的限流是全局精确的(跨所有 Worker):
# 不使用共享内存的限流(如果存在这种实现):
# Worker 0: 50 r/s
# Worker 1: 50 r/s
# 总实际: 100 r/s(超过预期的 50 r/s)
# 使用共享内存的限流(Nginx 的实际实现):
# Worker 0: 检查共享计数器
# Worker 1: 检查共享计数器
# 总实际: 精确 50 r/s ✓
# 限流测试
ab -n 1000 -c 50 http://localhost/api/test
# 观察返回 503 的比例
七、性能调优清单
7.1 系统级调优
# /etc/sysctl.conf — 内核网络参数
# 文件描述符
fs.file-max = 1000000
# TCP 连接
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# TIME_WAIT
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# TCP 缓冲区
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# 端口范围(Nginx 连接上游使用的源端口)
net.ipv4.ip_local_port_range = 1024 65535
# 应用
sysctl -p# /etc/security/limits.conf
# Nginx 用户的文件描述符限制
www-data soft nofile 65535
www-data hard nofile 655357.2 Nginx 配置调优
# nginx.conf 完整调优示例
user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
events {
worker_connections 65535;
multi_accept on;
accept_mutex off;
}
http {
# 基础
sendfile on; # 零拷贝发送文件
tcp_nopush on; # 优化 sendfile 的包大小
tcp_nodelay on; # 禁用 Nagle 算法
types_hash_max_size 2048;
# Keep-Alive
keepalive_timeout 65;
keepalive_requests 1000;
# 缓冲区
client_header_buffer_size 4k;
large_client_header_buffers 4 16k;
client_body_buffer_size 16k;
client_max_body_size 50m;
# 压缩
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 1000;
gzip_types text/plain text/css application/json
application/javascript text/xml;
# 日志
access_log /var/log/nginx/access.log main buffer=32k flush=5s;
# buffer: 缓冲日志,减少磁盘 I/O
# flush: 最多 5 秒刷新一次
# 开启日志条件写入(减少无用日志)
map $status $loggable {
~^[23] 0; # 2xx/3xx 不记录(可选)
default 1;
}
access_log /var/log/nginx/error_access.log main if=$loggable;
}
7.3 性能基准测试
# 使用 wrk 进行基准测试
wrk -t 4 -c 200 -d 30s http://localhost/
# -t 4: 4 个线程
# -c 200: 200 个并发连接
# -d 30s: 测试 30 秒
# 结果解读
# Requests/sec: 请求吞吐量
# Latency (Avg/Stdev/Max): 延迟分布
# Transfer/sec: 数据传输速率
# 测试不同 Worker 数的性能差异
for workers in 1 2 4 8; do
sed -i "s/worker_processes .*/worker_processes $workers;/" /etc/nginx/nginx.conf
nginx -s reload
sleep 2
echo "=== Workers: $workers ==="
wrk -t 4 -c 200 -d 10s http://localhost/ 2>&1 | grep "Requests/sec"
done
# 测试 Keepalive 对性能的影响
# 关闭 Keepalive
wrk -t 4 -c 200 -d 10s -H "Connection: close" http://localhost/
# 开启 Keepalive(wrk 默认)
wrk -t 4 -c 200 -d 10s http://localhost/八、总结
Master-Worker 模型是 Nginx 高性能的基础。Master 负责管理,Worker 负责处理请求。每个 Worker 独立运行事件循环,互不干扰。Worker 数设为 CPU 核心数,配合 CPU 亲和绑定。
epoll 事件驱动让单 Worker 处理数千并发连接。非阻塞 I/O + epoll 多路复用,一个 Worker 进程可以同时处理数千个连接而不需要线程切换。reuseport 选项解决惊群问题,是 Linux 3.9+ 环境的推荐配置。
Upstream Keepalive 是最重要的性能优化之一。没有 Keepalive,每个请求都需要 TCP 三次握手——延迟增加 1-2ms。配置
proxy_http_version 1.1和proxy_set_header Connection ""是 Keepalive 生效的前提。缓冲区设计保护了后端服务。Proxy Buffer 让 Nginx 快速读取后端响应、释放后端连接,再按客户端速度发送。这对后端的并发能力至关重要——没有缓冲,慢客户端会长时间占用后端连接。
调优是系统工程,不是改一个参数。从内核参数(somaxconn、tcp_tw_reuse)到 Nginx 配置(worker_connections、keepalive),再到应用层(缓冲区大小、日志策略)——需要从上到下一起调。
参考文献
- Nginx Documentation: Architecture (nginx.org/en/docs/)
- Nginx Source Code: Event Module (hg.nginx.org/nginx/file/tip/src/event/)
- Cloudflare Blog: SO_REUSEPORT Performance (blog.cloudflare.com/the-sad-state-of-linux-socket-balancing)
- Nginx Blog: Tuning Nginx for Performance (nginx.com/blog/tuning-nginx/)
- Linux man page: epoll(7), accept(2), setsockopt(2)
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】反向代理模式:TLS 终止、透传与重加密
系统解剖反向代理的三种 TLS 处理模式——终止、透传与重加密。从架构对比到 SNI 路由、证书管理、性能影响与安全权衡,给出生产环境的工程选型依据。
【网络工程】L7 负载均衡:HTTP 感知路由与内容交换
深入讲解 L7 负载均衡的工程实现:HTTP 请求解析与路由决策、基于 Host/Path/Header 的高级路由规则、SSL Termination 的架构位置、L4+L7 混合架构设计,以及 L7 LB 的性能优化与故障排查。
【网络工程】Socket 编程模型演进:从阻塞到多路复用
网络编程模型的选择决定了服务的并发能力上限。本文从阻塞 I/O 到非阻塞、select、poll、epoll,逐步解剖每种模型的系统调用开销、性能边界与适用场景,用 C 代码实测从 C10K 到 C1M 的演进。
【网络工程】epoll 深度剖析:ET/LT 模式、源码分析与性能特征
epoll 是 Linux 高性能网络编程的基石。本文深入剖析 epoll 的内核数据结构(红黑树与就绪链表)、ET 和 LT 两种触发模式的行为差异与编程范式、惊群问题及 EPOLLEXCLUSIVE 的解决方案。