5月28日 08:25

Nginx 如何优化静态资源?有哪些优化策略?

Nginx 如何优化静态资源?有哪些优化策略?

Nginx 处理静态资源的能力是面试高频考点。优化的核心思路是:减少磁盘 I/O、压缩传输体积、利用缓存避免重复请求、将负载推到边缘节点。下面从内核层到架构层逐级展开。

sendfile 与零拷贝:从内核直接发送

传统文件读取流程:磁盘 → 内核缓冲区 → 用户空间 → Socket 缓冲区 → 网卡,经历两次用户态拷贝。sendfile 让数据直接在内核态完成传输,省掉这两次拷贝,这是 Nginx 静态服务高性能的底层基础。

nginx
http { sendfile on; # 启用零拷贝,数据在内核态直接从文件描述符传输到 socket tcp_nopush on; # 在包头积累到最大后才发送,减少网络帧数 tcp_nodelay on; # 禁用 Nagle 算法,小包立即发送 }

面试追问:tcp_nopush 和 tcp_nodelay 看起来矛盾,为什么要同时开启?

tcp_nopush 在 sendfile 阶段生效:把 HTTP 响应头和文件内容拼成一个大数据块再发送,减少碎片帧;当最后一块数据不足 MSS 时,tcp_nodelay 接管,确保尾部数据不延迟发出。两者作用阶段不同,互不冲突。这是 Nginx 面试的经典追问。

Gzip 压缩:减小传输体积

文本类资源(HTML/CSS/JS/JSON/SVG)压缩收益显著,通常可减少 60%-80% 体积。但图片和视频本身已是压缩格式,再做 Gzip 反而浪费 CPU 且体积几乎不变。

nginx
http { gzip on; gzip_vary on; # 添加 Vary: Accept-Encoding,帮助 CDN 区分压缩/非压缩版本 gzip_min_length 1024; # 小于 1KB 不压缩,压缩收益抵不过开销 gzip_comp_level 6; # 压缩级别 1-9,6 是速度与压缩率的最佳平衡点 gzip_types text/plain text/css text/javascript application/json application/javascript application/xml image/svg+xml; gzip_static on; # 优先发送预压缩的 .gz 文件,避免实时压缩消耗 CPU }

gzip_static 的意义: 生产环境建议在构建阶段预生成 .gz 文件,Nginx 直接发送预压缩文件,零 CPU 开销。gzip on 作为兜底,当 .gz 文件不存在时实时压缩。

浏览器缓存:避免重复请求

缓存策略的核心是根据资源更新频率设置不同的过期时间。带哈希的静态资源(如 app.a3b2c1.js)可以长期缓存并标记 immutable,HTML 入口文件则需要短缓存或必须重新验证。

nginx
server { # 带哈希的静态资源:长期缓存 + immutable location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; # 静态资源不写日志,减少磁盘 I/O } # HTML 文件:短期缓存,必须重新验证 location ~* \.html$ { expires 1h; add_header Cache-Control "public, must-revalidate"; } }

面试追问:immutable 是什么意思?

告诉浏览器,资源在过期前不会变化,不需要发条件请求(If-Modified-Since / If-None-Match)验证。配合文件名哈希使用,用户刷新页面也不会产生 304 请求,直接从本地缓存读取。这在 HTTP RFC 8246 中被标准化。

文件描述符缓存:减少系统调用

Nginx 对频繁访问的文件可以缓存文件描述符(fd),避免每次请求都执行 open() / stat() 系统调用。在高并发场景下,这个优化效果显著。

nginx
http { open_file_cache max=10000 inactive=20s; open_file_cache_valid 30s; # 每 30s 验证缓存项是否仍有效(检查文件是否被修改) open_file_cache_min_uses 2; # 20s 内访问少于 2 次则移除,避免冷文件占用缓存 open_file_cache_errors on; # 缓存文件不存在等错误,防止同一 404 反复穿透磁盘 }

内存开销:每个缓存项约占 256 字节,10000 项约 2.5MB,对现代服务器可忽略。但 open_file_cache_errors on 要注意,如果频繁请求不存在的文件,错误缓存会占用大量条目。

动静分离与 CDN

将静态资源部署到独立域名或 CDN 有三个核心收益:

  1. 突破浏览器同域并发限制——浏览器对同一域名通常限制 6 个并发连接,独立域名可以并行下载更多资源
  2. 减少主站 Cookie 污染——静态资源请求不携带主站 Cookie,减少请求体积
  3. 边缘节点就近分发——CDN 将资源缓存到离用户最近的节点,大幅降低延迟
nginx
server { listen 80; server_name example.com; # 方案一:静态资源独立目录 location /static/ { root /var/www/static; expires 1y; add_header Cache-Control "public, immutable"; access_log off; } # 方案二:直接重写到 CDN location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2)$ { return 301 https://cdn.example.com$request_uri; } }

HTTP/2 与资源预加载

HTTP/2 多路复用已大幅降低多请求的开销,但资源预加载仍然有效。需要注意:Chrome 106+ 已移除 HTTP/2 Server Push 支持,推荐使用 <link rel="preload"> 替代。

nginx
server { listen 443 ssl http2; # 使用 Link header 预加载关键资源 location = / { add_header Link "</css/style.css>; rel=preload; as=style, </js/app.js>; rel=preload; as=script"; } }

preload vs prefetch: preload 告诉浏览器当前页面一定会用到该资源,立即下载;prefetch 表示下一页可能用到,空闲时下载。面试中常考两者区别。

图片与字体优化

nginx
server { # WebP 自动转换:浏览器支持 WebP 时优先返回 location ~* \.(jpg|png)$ { try_files $uri$webp_suffix $uri =404; expires 1y; add_header Cache-Control "public, immutable"; add_header Vary Accept; # 告诉 CDN 根据 Accept 头缓存不同版本 } # 字体文件:CORS 支持 + 长缓存 location ~* \.(woff2?|ttf|otf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; add_header Access-Control-Allow-Origin "*"; access_log off; } # 图片防盗链 location ~* \.(jpg|jpeg|png|gif)$ { valid_referers none blocked example.com *.example.com; if ($invalid_referer) { return 403; } } }

WebP 相比 JPEG/PNG 通常可减少 25%-35% 体积,配合 CDN 的 Vary: Accept 头可以同时缓存原始格式和 WebP 格式。

静态资源合并

CSS/JS 合并减少请求数,但在 HTTP/2 场景下收益降低。是否合并需根据实际场景权衡:

  • HTTP/1.1 环境:合并有效,减少连接开销
  • HTTP/2 环境:多路复用已解决队头阻塞,合并收益有限,反而影响缓存命中率
nginx
# 使用 ngx_http_concat_module(淘宝开源模块) # 访问 /static/css/??a.css,b.css,c.css 可合并返回 location /static/css/ { concat on; concat_types text/css; concat_max_files 10; }

安全与日志优化

nginx
server { # 禁止访问隐藏文件(.git、.env 等敏感文件) location ~ /\. { deny all; access_log off; log_not_found off; } # 静态资源关闭 access_log,减少磁盘写入 location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?|ttf|eot)$ { access_log off; } }

隐藏文件泄露是常见的安全漏洞。.git 目录暴露可能导致源码泄露,.env 暴露可能泄露数据库密码和 API Key。

生产环境完整配置参考

nginx
user nginx; worker_processes auto; # 自动匹配 CPU 核心数 worker_rlimit_nofile 65535; # 提升进程级文件描述符上限 events { worker_connections 10240; # 每个 worker 的最大连接数 use epoll; # Linux 下使用 epoll 事件模型 multi_accept on; # 一次性接受所有新连接 } http { # 内核层优化 sendfile on; tcp_nopush on; tcp_nodelay on; # 传输层优化 gzip on; gzip_vary on; gzip_min_length 1024; gzip_comp_level 6; gzip_types text/plain text/css text/javascript application/json application/javascript application/xml image/svg+xml; gzip_static on; # 文件系统缓存 open_file_cache max=10000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on; # 按内容类型动态设置缓存时间 map $sent_http_content_type $expires { default off; text/html 1h; text/css 1y; application/javascript 1y; ~image/ 1y; ~font/ 1y; } server { listen 80; server_name example.com; root /var/www/html; # 静态资源长缓存 location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?|ttf|eot)$ { expires $expires; add_header Cache-Control "public, immutable"; access_log off; } # 字体 CORS location ~* \.(woff2?|ttf|otf|eot)$ { add_header Access-Control-Allow-Origin "*"; } # 禁止隐藏文件 location ~ /\. { deny all; } location / { try_files $uri $uri/ =404; } } }

优化策略速查表

层级策略核心指令收益
内核层零拷贝传输sendfile + tcp_nopush + tcp_nodelay减少 CPU 拷贝和网络碎片帧
传输层Gzip 压缩gzip + gzip_static文本资源体积减少 60%-80%
缓存层浏览器缓存expires + Cache-Control immutable避免重复请求,零带宽消耗
缓存层文件描述符缓存open_file_cache减少 open/stat 系统调用
架构层动静分离 + CDN独立域名 + CDN 回源突破并发限制 + 就近分发
协议层HTTP/2 + Preloadhttp2 + Link header多路复用 + 关键资源预加载
格式层WebP + WOFF2try_files + Vary Accept图片体积再减 25%-35%
安全层防盗链 + 隐藏文件valid_referers + deny防止资源盗用和源码泄露

面试中回答这类问题,建议按"内核优化 → 传输优化 → 缓存策略 → 架构设计"的层次递进,体现系统思维。每个策略说清原理、配置和收益,比单纯罗列配置更有说服力。如果面试官追问某一项的细节,可以深入到内核原理层面(如 sendfile 的 DMA 实现、epoll 的 LT/ET 模式),展示技术深度。

标签:Nginx