杭州品茶网,Nginx调优深度解析:10个技巧让网站速度提升300%
压测结果出来了:QPS 从约 800 提升到接近 3.5k,95% 响应时间从 900ms 降到 120ms,带宽消耗下降约 60%,代理缓存命中率稳定在 75% 以上。后端请求量明显减少,磁盘写入压力也轻了不少。数据说话,改动确实起了作用。
拿到这些数字后,首要工作是验证和监控。用 ab/wrk 做了多轮压测,命令像 wrk -t12 -c400 -d60 http://域名/;每次变更后跑同样的脚本对比数据。同时把 nginx 的指标接入 Prometheus,通过 nginx-vts-exporter 拉取 request rate、active connections、proxy_cache 的命中率、upstream 状态等数据,再用 Grafana 做面板。看到曲线稳定之后,才敢在生产面前放手。顺带一提,看到缓存命中从几乎零飙到七成多,心里是有点小激动的——毕竟少了那么多后端压力。
在监控之上,做了一套快速报警和可视化:nginx 的 stub_status 暴露基础连接数,然后 nginx-vts 提供更细的缓存、后端状态。Prometheus 抓到关键值后设了阈值告警,比如 active connections 超过 80% 的历史峰值就报警;cache hit 低于 50% 也会触发。这个流程很重要,别只靠压测数字,线上波动随时可能冒出来。
回过头说具体改了哪些配置。先讲 HTTP 层的协议优化:把 HTTPS 端口改为 listen 443 ssl http2,启用了 HTTP/2 的多路复用,头部压缩也起作用。TLS 用的是 TLS1.3,禁掉了旧版 TLSv1 和 TLSv1.1,Ciphers 选了现代套件,开启了 session resumption。这样做的直观好处是同一连接能同时承载更多请求,尤其是页面上有大量小资源时效果明显。
紧接着是压缩和缓存策略。把 Gzip 打开,针对 text、html、css、js、json、xml 这些类型做压缩,设置了 gzip_min_length(避免对超小文件浪费 CPU)、gzip_types 明确列出要压的 MIME 类型、gzip_vary 打开以兼容 CDN 缓存。测试时发现,文本资源传输体积平均只剩下原来的 25% 左右,移动端用户体验提升明显。浏览器缓存方面,对于静态资源设了 Cache-Control: max-age=31536000, immutable,这样图片、版本化的 JS/CSS 可以在客户端长期缓存;对于需要频繁更新的资源则设置短期缓存或不缓存。
代理缓存(proxy_cache)也下了很大的功夫。为动态渲染但能短期缓存的接口配置了 proxy_cache_path,指定 keys_zone 名称、levels 目录结构、inactive 时间以及 max_size。proxy_cache_key 带上请求的 Host、URI、query string 以及一些 header 来保证缓存粒度合适。对后端接口做了合理的缓存规则:某些 GET 接口缓存 30s 到 60s,用户量大的页面缓存更长。这样能把重复击穿的请求留在 Nginx 层解决,减少后端压力。
在 Nginx 与后端之间的缓冲和超时也调整了。把 proxy_buffer_size、proxy_buffers、proxy_busy_buffers_size 拉大,防止大响应打满内存时直接写磁盘,proxy_connect_timeout、proxy_send_timeout、proxy_read_timeout 设置为合理值以避免挂起连接耗资源。对于长连接接口,设置了
proxy_cache_background_update 来避免缓存过期时同时打到源站的暴击。
并发处理方面做了两件事:进程与连接数的调整,以及操作系统层面的 TCP 参数优化。Nginx 把 worker_processes 设置为 CPU 核心数或使用 auto,让进程数量与核数匹配,worker_connections 则根据每个进程的预计并发设置(比如 10240)。还打开了 epoll、reuseport、sendfile、tcp_nopush、tcp_nodelay 这些常用选项。OS 层面,调整了 net.core.somaxconn、net.ipv4.tcp_tw_reuse、tcp_fin_timeout、
net.ipv4.tcp_max_syn_backlog 等参数,Keepalive 设置上把 keepalive_timeout 和 keepalive_requests 调整为更适合长连接场景的值,避免频繁握手带来的开销。实测后,连接建立和断开消耗都下降,连接复用率提升。
日志方面做了精简与轮转。高并发场景下 access_log 会吃掉大量 IO,采取了条件记录(用 map 把某些静态资源或健康探测请求过滤掉),只记录关键信息,减轻磁盘压力。配合 logrotate 做定期归档和压缩,防止日志文件无限膨胀。另一个手段是利用 geo 和 allow/deny 阻挡明显的恶意 IP 段,在 nginx 层先拦截一批垃圾流量,减少后端无意义负载。
静态资源方面的优化不在 Nginx 本身合并文件,因为 Nginx 不是打包工具。实际做法是用前端构建工具(Webpack、Rollup)做代码分割、压缩与合并,打出带 hash 的静态文件,然后把这些静态文件放到 CDN 上,利用边缘缓存把用户请求就地解决。Nginx 主要负责把正确的缓存头塞进去,和做一些回源控制。放到 CDN 后,全球的延迟就降低了不少,这对国际化或多地域用户很关键。
为了评估每次改动的效果,按步骤跑了 AB/WRK 压测:先baseline,再逐项改动并压测对比,记录 QPS、平均响应、p95、失败率和后端请求数。每次只改一项,避免改动叠加带来的判断歧义。压测命令在上文提到,基本套路是并发和线程都设置到接近业务峰值的倍数,然后观察系统是否进入饱和。
还有监控扩展:除了 nginx 内部状态,接入了系统层面的 iostat、vmstat 和 netstat,看磁盘、内存、网络的瓶颈。把这些数据都汇到 Grafana,能直观看到哪一层先到瓶颈点,是网络带宽、磁盘 IO 还是后端吞吐。实践中多次发现瓶颈并不是 Nginx 的配置单一问题,而是链路中某个节点限制了整体表现。
整个改造过程中,研发和运维是两支主要力量。运维负责底层参数调整、滚动发布配置和监控告警;研发负责接口是否允许缓存、是否可以做请求幂等等配合。改动上线时采用了灰度发布和分段流量切换,先把低峰或部分流量切到新配置上观察,再放大范围。这样风险可控,出现问题也好回滚。
最后回到起点:为什么做这些事。业务是一个同时承载静态内容和动态请求的中大型网站,默认的 Nginx 配置在高并发、移动端占比高的场景下,容易成为瓶颈。带宽有限、后端服务承受力有限、日志和 IO 压力大,这些现实问题推动我们逐项优化。从协议层、缓存层、连接管理到监控告警,逐步拆解问题才有现在的提升。看着那些曲线和数字变化,老实说,干活有回报的感觉还是挺实在的。