Nginx 性能调优实战:worker、gzip 与缓存加速

当一个站点的流量从日均几千涨到几万,最先扛不住的往往不是数据库,而是门口那台 Nginx。很多团队把 Nginx 当成「能转发就行的反向代理」,沿用手写或半默认配置跑生产,结果会出现几类典型症状:CPU 只有单个核跑满、其余核在围观;并发一高就出现大量 TIME_WAIT 或连接排队;静态资源每次请求都被重新压缩、带宽翻倍却没人察觉。本文聚焦 Nginx 性能调优 的实战路径,从 worker 进程、gzip/brotli 压缩、静态缓存,到 HTTP/2、连接复用、文件缓存与内核配合,每一项都给出可直接抄用的配置片段,并用 wrk 做前后压测对照,帮你把 QPS 顶上去、把首屏压下来。

一、先量再调:用 wrk 建立基线

调优最忌「凭感觉改参数」。动手前先用 wrk 打一个基线,记下 QPS、延迟 P99 和传输体积,后面每一项改动都回测对比,才知道到底有没有用、收益有多大。建议至少跑两轮取中位数,避免机器临时抖动干扰判断。

wrk -t4 -c200 -d30s http://127.0.0.1:8080/
# 关注输出:Requests/sec、Latency 的 99% 分位、Transfer/sec

如果你的 Nginx 是用容器部署的,也可以把它和后端服务一起放进 Docker 编排 里做整体压测,避免单机变量干扰结论。

二、worker 进程与连接数:把多核吃满

Nginx 是 master-worker 模型,默认 worker_processes 只有 1,等于把多核 CPU 白白浪费。第一步就是让它按核数开进程,并放开文件描述符上限。在 Linux 上 Nginx 使用 epoll 事件模型,配合 worker_cpu_affinity 把进程绑到具体核心,能减少跨核缓存失效带来的上下文切换开销。

worker_processes auto;          # 按 CPU 核数自动开进程
worker_cpu_affinity auto;       # 绑定核心,减少上下文切换
worker_rlimit_nofile 100000;   # worker 能打开的文件描述符上限

events {
    worker_connections 10240;   # 单 worker 最大连接数
    use epoll;                 # Linux 下使用 epoll 事件模型
    multi_accept on;           # 一次 accept 多个新连接
}

关键约束:worker_rlimit_nofile 必须 ≥ worker_connections × worker_processes,否则高并发下会报「too many open files」。同时别忘了系统层也要放开:执行 ulimit -n 100000 或修改 /etc/security/limits.conf,否则 Nginx 配置写得再大也被内核卡住。关于文件描述符耗尽的排障,可延伸阅读 服务器安全加固 中的系统限制章节。

三、开启 gzip 与 brotli 压缩

文本类响应(HTML/CSS/JS/JSON)压缩率通常在 70% 以上,是性价比最高的优化。Nginx 原生支持 gzip,若编译了 ngx_brotli 模块还能上压缩率更高的 brotli。注意 gzip_proxied 要设为 any,否则反代后端返回的已压缩响应不会被再次处理;若追求极致,可以用 brotli 把静态资源预压缩成 .br 文件,用 try_files 直接命中。

gzip on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_proxied any;
gzip_types text/plain text/css text/javascript application/json application/javascript image/svg+xml;
gzip_vary on;

# 若编译了 brotli 模块
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css text/javascript application/json application/javascript image/svg+xml;
指令推荐值作用
gzip_comp_level6压缩率与 CPU 消耗的平衡点
gzip_min_length1024小于 1KB 不压缩,避免负收益
gzip_types含 application/javascript漏写 JS 会导致脚本未压缩
gzip_proxiedany反代场景必须开,否则后端响应不压缩
brotli_comp_level6现代浏览器额外再省 15%~20%

一个高频坑:gzip_types 里漏写 application/javascript,结果 JS 包每次都明文传输,白白多耗几倍的带宽和首屏时间。

四、静态资源缓存:少一次请求,少一次回源

图片、字体、打包后的 JS/CSS 几乎不会变,给它们长缓存 + 内容哈希文件名,可以把回源压力降到极低。配合 Cache-Control: immutable,浏览器在资源未过期前连条件请求都不会发。注意 HTML 文档本身要短缓存(比如 60s 或 no-cache),否则发版后用户看不到更新。

location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2?|ttf)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
    access_log off;          # 静态资源不打访问日志,省磁盘 IO
}

location = /index.html {
    add_header Cache-Control "no-cache";   # HTML 短缓存,保证发版可见
}

缓存策略要和反代配合,关于反向代理的基础配置可看 Nginx 反向代理实战;如果前面还挂了负载均衡,缓存命中率会进一步放大整体吞吐,参见 Nginx 负载均衡。

五、连接复用:upstream keepalive

反向代理场景下,Nginx 到后端默认每条请求新建一个 TCP 连接,三次握手加慢启动非常浪费。用 upstream keepalive 复用连接能显著降延迟。记得在 location 里把 proxy_http_version 设为 1.1 并清空 Connection 头,否则 keepalive 不会生效;同时用变量拼 proxy_pass(如 proxy_pass $upstream_url)会破坏复用,应直接用 upstream 名称。

upstream backend {
    server 10.0.0.11:8080;
    server 10.0.0.12:8080;
    keepalive 32;            # 每个 worker 保留的长连接数
    keepalive_timeout 60s;
}

location /api/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Connection "";   # 关键:清空 Connection 让 keepalive 生效
    proxy_set_header Host $host;
}

六、启用 HTTP/2 与 TLS 加速

HTTP/2 的多路复用能消除队头阻塞,前端再也不用做「域名分片」这种 hack;配合 TLS 1.3、会话复用和 OCSP stapling,既快又省握手开销。现代浏览器默认走 HTTPS,TLS 握手往往比应用逻辑更耗时,这几行值得认真配。

listen 443 ssl http2;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/ca.pem;

七、缓冲、超时与内核配合

sendfile、tcp_nopush、tcp_nodelay 这三个指令让 Nginx 用最少的系统调用把文件甩给网卡;超时值则要压短,避免慢连接长期占着 worker。生产环境尤其要把 client_body_timeout、proxy_read_timeout 调小,防止个别慢客户端拖垮整池连接。

sendfile on;
tcp_nopush on;       # 等包凑满再发,减少小包
tcp_nodelay on;      # 对交互型连接立即发送
client_body_timeout 10s;
proxy_read_timeout 30s;
keepalive_timeout 65s;

八、open_file_cache:给文件元数据也加缓存

很多人只调 gzip 和连接数,却忘了 Nginx 每次服务静态文件都要 open()、stat() 一次文件。open_file_cache 把文件描述符、大小和修改时间缓存起来,对大量小文件的场景提升明显,是性价比被严重低估的一行配置。

open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

九、用 stub_status 看实时连接

调优不能靠猜,得看得见。开启 stub_status 后,用 curl 就能拿到当前活跃连接数、accepts/handled/requests 计数和 waiting 连接数,压测时盯着它就能判断瓶颈在连接池还是后端。

location /nginx_status {
    stub_status on;
    allow 127.0.0.1;
    deny all;
}
# curl http://127.0.0.1/nginx_status

十、容器与 K8s 部署的注意点

在容器或 Kubernetes 里跑 Nginx,除了上面这些,还要留意两点:一是容器默认 ulimit 可能很小,需要在 Deployment 里设置 securityContext 或 init 容器调高 nofile;二是 K8s 的 CPU limit 会触发 CPU 限流(throttling),把 worker_processes 设成 auto 后若被 limit 卡住,多核反而发挥不出来,必要时给足 request/limit 或改用静态核数。关于用 Docker 编排多服务,可参考 Docker Compose 一键编排。

十一、回测与常见坑

改完再用 wrk 打一遍,正常情况下 QPS 应有 1.5~3 倍提升,Transfer/sec 因为压缩也应明显下降。常见坑:

  • gzip_types 漏写 application/javascript,导致 JS 没压缩;
  • gzip_proxied 没开,反代后端响应原样透传不压缩;
  • expires 给到 1y 却没给文件加内容哈希,发版后用户一直拿旧资源;
  • 只开 gzip 不开 brotli,现代浏览器白白损失 15%~20% 压缩率;
  • worker_connections 配得很大但系统 ulimit 没放开,高并发直接 502;
  • 用变量拼 proxy_pass,导致 upstream keepalive 失效、连接数暴涨。

十二、最小可用调优清单

如果只想先抄一份,把第二、三、四、五、八节的核心指令合并即可,上线前务必用 wrk 回归对照。Nginx 本身的能力边界与安装部署,可回顾 Nginx 入门。调优是持续过程,建议把压测脚本接进 GitHub Actions 做每次合并前的性能门禁,防止配置悄悄回退;日常排障也可以在 VS Code 里配合远程 SSH 插件直接改配置、看日志。

上一篇 Spring AOP 实战:用切面统一日志、事务与权限
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]