Nginx 调优别急着改参数,先把这三笔账算清楚
很多人第一次打开 nginx.conf,看到 worker_processes 和 worker_connections 就开始改,改完重启,发现 QPS 没怎么动。真正卡住的地方往往不在这些参数上——可能是磁盘 IO 顶满了,可能是后端只开了一个 PHP-FPM 进程,也可能是 TLS 握手把 CPU 吃掉了。调优这件事,先定位再动手,比背参数表有用得多。
下面按三种最常见的场景拆开说,每种场景的瓶颈位置不一样,改法也不一样。如果你只是跑一个静态站,重点在文件描述符和 sendfile;如果是反向代理,重点在缓冲和超时;如果开了 HTTPS,重点在会话复用。
静态资源站:先把文件描述符和 sendfile 拉满
纯静态站点的瓶颈通常在两个地方:文件描述符上限和内核态的零拷贝。Nginx 默认 worker_connections 是 512,配合 worker_processes auto,一台 4 核机器理论并发上限在 2048 左右。但真正上线跑的站点,浏览器一个页面可能开 6~8 个连接,2000 并发撑不住多少真实用户。
我一般会先把 worker_connections 调到 10240,同时确认系统的 nofile 也放开了。只改 nginx 不改系统限制,重启后会报「worker_connections exceed open file resource limit」,白改。
- worker_processes auto; 让 Nginx 按 CPU 核数自动起进程,4 核就是 4 个 worker
- worker_connections 10240; 单 worker 能处理的连接数,配合 worker_rlimit_nofile 一起设
- sendfile on; 静态文件走内核零拷贝,省掉用户态到内核态的一次数据搬运
- tcp_nopush on; 配合 sendfile,把响应头和文件内容攒在一个包发出
这四项改完,一台 2 核 4G 的机器跑静态站,并发从 800 左右能提到 3000 上下。代价是内存占用会涨一点,每个连接要占几 KB 的缓冲区,10240 连接大概多吃几十 MB,对现在的机器来说不算什么。
如果静态文件比较多,可以再加 open_file_cache。这个参数缓存的是文件描述符,不是文件内容,命中后省掉一次 open() 系统调用。max 设 10000、inactive 设 60s 是比较稳的起点。设太大反而会占内存。
反向代理场景:缓冲和超时才是真正的坑
代理场景下 Nginx 自己不怎么吃 CPU,瓶颈基本都在后端。但有几个参数设错了,会让后端白白扛压力。
proxy_buffering 默认是开的,Nginx 会先把后端响应缓存在自己的缓冲区,再慢慢发给客户端。如果后端返回的是大文件或者流式接口,缓冲反而会让客户端等更久。我遇到过接口响应只有 200ms、但客户端要 2s 才收到的情况,就是缓冲在中间卡了一下。
proxy_buffers 和 proxy_buffer_size 这两个要一起调。默认 proxy_buffer_size 是 4k 或 8k,如果后端返回的响应头超过这个大小,Nginx 会报「upstream sent too big header」。把 proxy_buffer_size 提到 16k、proxy_buffers 设成 8 16k,基本能覆盖大多数场景。
超时这块,proxy_connect_timeout 默认 60s 太长了。后端挂了,客户端要等 60 秒才收到 502,体验很差。我一般会设成 5s 到 10s,具体看后端正常响应时间。proxy_read_timeout 可以留长一点,60s 到 120s,避免大查询被误杀。
还有 keepalive。Nginx 到后端的连接默认是短连接,每个请求都要重新握手。在 upstream 块里加 keepalive 32,再在 location 里设 proxy_http_version 1.1 和 proxy_set_header Connection "",后端连接就能复用。这一项对 PHP-FPM 后端的 QPS 提升很明显,通常能涨 20% 到 30%。
如果后端和 Nginx 不在同一台机器,网络延迟会放大所有超时问题。这种场景下,把 Nginx 和后端放在同一个机房、同一个内网里,比调任何参数都管用。秀米云的香港大带宽服务器 I 是 E5-2630L*2 / 32G / 30M 的配置,月付 $193.50,同机房内网延迟通常在 0.1ms 到 0.3ms,代理层和后端放一起能省掉不少网络开销。
TLS 与连接复用:握手省下来的 CPU 比你想象的多
开了 HTTPS 之后,CPU 消耗会明显上去。一次完整的 TLS 握手大概要 1~2 次非对称加密运算,RSA 2048 大概占 1ms 到 2ms 的 CPU 时间。QPS 上千的站点,握手就能吃掉一两个核。
省 CPU 的办法有两个方向:减少握手次数,和换更轻的加密算法。
减少握手次数靠 session 复用。ssl_session_cache shared:SSL:10m 能缓存大约 40000 个会话,ssl_session_timeout 设 1h 到 24h。客户端第二次连接时直接复用会话,跳过完整握手。这一项对移动端用户效果最明显,弱网环境下重连频繁,复用率能到 70% 以上。
TLS 1.3 把完整握手从两次往返压到一次,还支持 0-RTT。如果客户端支持,升级到 TLS 1.3 能省掉一半的握手时间。ssl_protocols 里加上 TLSv1.3,同时保留 TLSv1.2 兼容老客户端。
加密套件方面,优先选 ECDHE 而不是 RSA。ECDHE 的密钥交换比 RSA 快不少,P-256 曲线下一次密钥交换大概 0.2ms,RSA 2048 要 1ms 左右。ssl_ciphers 里把 ECDHE 开头的套件排前面就行。
OCSP Stapling 也值得开。默认情况下浏览器要自己去 CA 查证书吊销状态,多一次网络请求。开启后 Nginx 定期去查,把结果缓存下来随握手一起发给客户端,省掉客户端的一次请求。
这些改完,一台 4 核机器跑 HTTPS 静态站,QPS 从 1500 左右能提到 2500 上下。代价是配置复杂度上去了,ssl_session_cache 的 shared 内存是常驻的,10m 大概占 10MB 内存,可以接受。
先量再调,别一上来就抄配置
上面说的这些参数,没有一个能包打天下。静态站和代理站的瓶颈完全在不同位置,TLS 又是另一回事。我一般会先用 ab 或者 wrk 压一轮,看 CPU、内存、磁盘 IO 哪个先到顶,再决定改哪里。
如果 CPU 先满,看是用户态还是内核态。用户态高通常是 TLS 或者 gzip 在吃 CPU,内核态高往往是连接数或者文件 IO 的问题。如果磁盘 IO 先满,静态站就上 SSD,代理站就调缓冲。
换我我会先花半小时压一轮,拿到基线数据再动手。改完一个参数压一次,确认有效再改下一个。一次性改十几个参数,出问题都不知道是哪个引起的。
最后说一句:Nginx 调优能解决的是单机极限,解决不了架构问题。如果单机压到极限还是扛不住,该上负载均衡就上,该拆服务就拆,别在参数里打转。