Nginx 性能调优
这是本系列的压轴章节。前面学了配置、虚拟主机、反向代理、HTTPS、gzip——这些已经能让 Nginx 跑起来。但要让 Nginx 真正发挥单机数万并发的潜力,需要一套系统的性能调优。这一章我们从进程模型、事件循环、零拷贝、长连接、系统限制五个层面,把硬件压榨到极致。
1. 调优前先测量
性能优化的第一原则:先测后调。不要拍脑袋改配置,每次只改一个参数、用同一套基准测试对比。否则你根本不知道改了有没有效果,甚至可能负优化。
常用的压力测试工具:
- ab(Apache Bench):简单,适合快速验证。
- wrk:多线程,更准确,社区主流。
- vegeta:Go 写的,支持复杂场景。
- JMeter:企业级,支持各种协议和插件。
2. worker_processes 与 worker_connections
这是 Nginx 性能的地基。worker 数量决定 CPU 利用率,每个 worker 的连接数决定并发上限:
# worker 进程与连接数调优
worker_processes auto; # auto = 自动等于 CPU 核数(最常用)
# 也可以显式指定:worker_processes 8;
events {
worker_connections 10240; # 每个 worker 最大连接数(默认 512,太保守)
multi_accept on; # 一次接受所有新连接(高并发必备)
use epoll; # Linux 默认就是 epoll,可省略
}
# 最大并发连接数 = worker_processes × worker_connections
# 4 核 × 10240 = 40960 并发
# 8 核 × 10240 = 81920 并发
# 反向代理场景要除以 2(每个客户端连接还要占一个到后端的连接)
# 8 核 × 10240 / 2 = 40960 真实客户端并发几个关键经验:
- worker_processes = CPU 核数(auto 即可),多了反而增加上下文切换开销。
- worker_connections 默认 512 太保守,生产推荐 10240 起步。
- 反向代理场景要除以 2(每个客户端连接对应一个后端连接)。
- 真正的瓶颈往往不是 Nginx,而是后端——别只调 Nginx。
3. 系统级文件描述符限制
worker_connections 设到 10240,但 Linux 默认每个进程只能开 1024 个文件描述符——这个限制会先于 Nginx 配置生效。必须同时调系统参数:
# 系统级文件描述符限制(worker_connections 受此约束)
# 查看当前限制
ulimit -n
# 1024 ← 默认值太小,需要调大
# 临时修改(仅当前 shell 生效)
ulimit -n 65535
# 永久修改:/etc/security/limits.conf
# 加两行:
# * soft nofile 65535
# * hard nofile 65535
# Nginx 配置里也可以单独设(推荐)
worker_rlimit_nofile 65535;
# systemd 系统(Ubuntu 16.04+)还要改 service 文件
# /etc/systemd/system/multi-user.target.wants/nginx.service
# [Service]
# LimitNOFILE=65535这是最常见的"我明明配了 worker_connections 10240,但 QPS 上不去"的原因。生产环境部署脚本里应该自动设置。
4. sendfile、tcp_nopush、keepalive(黄金三件套)
这三个指令能带来50% 以上的性能提升,几乎没有任何副作用,建议默认开启:
http {
# === 零拷贝:内核态直接传文件 ===
sendfile on; # 静态文件必开(性能提升 2-3 倍)
# === TCP 数据包优化 ===
tcp_nopush on; # 等数据包填满再发(配合 sendfile,减少网络包数)
tcp_nodelay on; # 立即发包(适合实时交互,与 tcp_nopush 互不冲突)
# === 长连接(HTTP keepalive)===
keepalive_timeout 65; # 客户端长连接超时(秒),0 = 关闭
keepalive_requests 1000; # 一个长连接上最多处理多少请求(默认 1000)
# === 后端长连接(反向代理场景重要!)===
upstream backend {
server 10.0.0.1:3000;
keepalive 32; # 缓存 32 个到后端的长连接
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # 必须 1.1 才能复用长连接
proxy_set_header Connection ""; # 清空 Connection 头
}
}
}原理详解:
- sendfile on:传统读文件要"用户态→内核态→用户态→内核态"4 次拷贝,sendfile 让内核直接把文件送到 socket,零拷贝,CPU 和内存双省。
- tcp_nopush on:等数据包攒够再发,减少 TCP 包数量(和 sendfile 配合,传输大文件时减少 30%-50% 的包)。
- tcp_nodelay on:和 tcp_nopush 听起来矛盾,但其实是不同时机——前者适合大文件,后者适合实时小响应。同时开启是常见做法。
- keepalive_timeout:长连接超时,65 秒是经典值。设太长会占连接,太短会重复握手。
- upstream keepalive:反向代理时缓存到后端的长连接,能省下大量 TCP 握手开销,是反向代理性能的关键。
5. 缓冲区与超时
合理的缓冲区能减少磁盘 I/O,但太大反而浪费内存:
http {
# === 客户端缓冲区 ===
client_body_buffer_size 16k; # 请求体缓冲区(超过会写磁盘)
client_body_timeout 60s; # 请求体接收超时
client_header_timeout 60s; # 请求头接收超时
client_max_body_size 20m; # 请求体最大大小(超过返 413)
# === 大文件下载优化 ===
output_buffers 2 32k; # 响应输出缓冲区
postpone_output 1460; # 累积到 1460 字节再发(一个 TCP 包大小)
# === connection_pool(高级)===
# 每 worker 的连接内存池,一般不用改
connection_pool_size 512;
request_pool_size 4k;
}几个经验值:
- client_max_body_size:上传场景调大(如 100m),普通场景 1m 够用。设太大有 DoS 风险。
- client_body_buffer_size:超过此值的请求体会写到磁盘临时文件,影响性能,按业务调。
- 各种 timeout:太长会被慢客户端占连接,太短会误杀正常请求,60 秒是平衡值。
6. 客户端缓存:用浏览器省服务器
性能优化最划算的方式不是"让服务器更快",而是不让请求到达服务器。用 HTTP 缓存头让浏览器直接用本地副本:
# 客户端缓存:用 expires 控制 Cache-Control / Expires 头
server {
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2)$ {
root /var/www/static;
# 强缓存:30 天内浏览器直接用本地缓存,不发请求
expires 30d;
add_header Cache-Control "public, immutable";
# immutable 让浏览器连 304 验证都不发(适合带 hash 的文件名)
}
location ~* \.(html)$ {
# HTML 不能强缓存(更新后用户看不到),用协商缓存
add_header Cache-Control "no-cache";
# 浏览器每次发请求问服务器"改了没",没改返 304
}
location = /index.html {
add_header Cache-Control "no-store";
# 完全不缓存(每次都重拉)
}
}缓存策略的最佳实践:
- 带 hash 的静态资源(如 app.abc123.js):强缓存 1 年(immutable)。文件变了 hash 也变,自动失效。
- HTML 入口文件:协商缓存(no-cache),每次问一下服务器。
- API 响应:根据业务,敏感数据 no-store,公共数据可缓存几秒。
- CDN + Cache-Control:把静态资源放到 CDN,进一步减少源站压力。
7. 静态资源优化清单
- 开启 sendfile + tcp_nopush(已经在黄金三件套里)。
- 开启 gzip / brotli 压缩(详见 gzip 章节)。
- 设置合理的 Cache-Control 与 expires。
- 小图标合并成 sprite 或用 iconfont。
- 图片用 webp / avif 替代 jpg/png,体积减半。
- 大文件用 Range 请求支持断点续传:
max_ranges指令。 - 开启 open_file_cache 缓存文件描述符:
open_file_cache max=2000 inactive=20s;
8. HTTP/2 与 HTTP/3
HTTP/2 自带多路复用、头部压缩、服务器推送,对页面加载性能提升明显(HTTPS 章节讲过怎么开)。HTTP/3 基于 QUIC(UDP),进一步减少握手延迟,Nginx 1.25.0+ 开始支持,配置:
- 需要 Nginx 编译时加
--with-http_v3_module; - 需要 UDP 443 端口放通;
- 需要 SSL 证书 + quic 专用配置;
- 响应头加
Alt-Svc让浏览器知道有 HTTP/3 可用。
9. 实测对比
调优后用压测工具验证:
# 1. 用 ab(Apache Bench)测 QPS
ab -n 10000 -c 100 http://localhost/
# -n 10000 总请求数
# -c 100 并发数
# 关注:Requests per second、Time per request、Failed requests
# 2. 用 wrk 测(更准确)
wrk -t8 -c1000 -d30s http://localhost/
# -t8 8 个线程
# -c1000 1000 并发连接
# -d30s 跑 30 秒
# 3. Nginx 自带状态页(需编译 stub_status 模块)
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
# 访问 /nginx_status 看到:
# Active connections: 15
# server accepts handled requests
# 8456 8456 32891
# Reading: 0 Writing: 1 Waiting: 14
# 4. 查看 Nginx 进程的 CPU 占用
top -p $(pgrep -d',' nginx)典型优化前后对比(4 核 8G 服务器,纯静态 1KB 文件):
| 状态 | QPS | P99 延迟 |
|---|---|---|
| 默认配置 | ~8000 | 50ms |
| + worker_connections 10240 | ~15000 | 40ms |
| + ulimit + sendfile | ~25000 | 30ms |
| + gzip + open_file_cache | ~30000 | 25ms |
注意:测试要在不同机器上做,结果仅供参考。重点是掌握调优方法论,而不是记住具体数字。
10. 调优检查清单(生产必跑)
worker_processes auto+worker_connections 10240;worker_rlimit_nofile 65535+ 系统ulimit -n 65535;sendfile on+tcp_nopush on+tcp_nodelay on;keepalive_timeout 65+ upstreamkeepalive 32;- gzip 开启 + gzip_types 覆盖所有文本类型;
- 静态资源 expires 30d + immutable;
- HTTP/2 开启;
- error_log 用 warn,access_log 必要时关闭;
- 定期 logrotate 切割日志;
- 用
nginx -t测试、用压测工具验证。
系列总结
恭喜你完成了Nginx 10 篇完整系列!从最基础的"什么是 Nginx",到生产级的反向代理、HTTPS、性能调优,你现在应该能:
- 独立搭建一个生产可用的 HTTPS 静态站点;
- 配置前后端分离项目的反向代理与负载均衡;
- 用 gzip、HTTP/2、缓存策略把页面性能优化到极致;
- 读懂 Nginx 日志,定位线上性能瓶颈;
- 扛住单机数万并发,配合 K8s/Docker 横向扩展。
下一步推荐:OpenResty(Nginx + Lua)解锁编程能力、Kong API 网关、Envoy(云原生代理)。Nginx 是基础,掌握了它,学这些上层工具都会得心应手。
← 上一篇 日志系统
← 返回 Nginx 教程目录