Nginx 如何优化静态资源?有哪些优化策略?
Nginx 如何优化静态资源?有哪些优化策略?
Nginx 处理静态资源的能力是面试高频考点。优化的核心思路是:减少磁盘 I/O、压缩传输体积、利用缓存避免重复请求、将负载推到边缘节点。下面从内核层到架构层逐级展开。
sendfile 与零拷贝:从内核直接发送
传统文件读取流程:磁盘 → 内核缓冲区 → 用户空间 → Socket 缓冲区 → 网卡,经历两次用户态拷贝。sendfile 让数据直接在内核态完成传输,省掉这两次拷贝,这是 Nginx 静态服务高性能的底层基础。
nginxhttp { 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 且体积几乎不变。
nginxhttp { 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 入口文件则需要短缓存或必须重新验证。
nginxserver { # 带哈希的静态资源:长期缓存 + 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() 系统调用。在高并发场景下,这个优化效果显著。
nginxhttp { 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 有三个核心收益:
- 突破浏览器同域并发限制——浏览器对同一域名通常限制 6 个并发连接,独立域名可以并行下载更多资源
- 减少主站 Cookie 污染——静态资源请求不携带主站 Cookie,减少请求体积
- 边缘节点就近分发——CDN 将资源缓存到离用户最近的节点,大幅降低延迟
nginxserver { 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"> 替代。
nginxserver { 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 表示下一页可能用到,空闲时下载。面试中常考两者区别。
图片与字体优化
nginxserver { # 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; }
安全与日志优化
nginxserver { # 禁止访问隐藏文件(.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。
生产环境完整配置参考
nginxuser 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 + Preload | http2 + Link header | 多路复用 + 关键资源预加载 |
| 格式层 | WebP + WOFF2 | try_files + Vary Accept | 图片体积再减 25%-35% |
| 安全层 | 防盗链 + 隐藏文件 | valid_referers + deny | 防止资源盗用和源码泄露 |
面试中回答这类问题,建议按"内核优化 → 传输优化 → 缓存策略 → 架构设计"的层次递进,体现系统思维。每个策略说清原理、配置和收益,比单纯罗列配置更有说服力。如果面试官追问某一项的细节,可以深入到内核原理层面(如 sendfile 的 DMA 实现、epoll 的 LT/ET 模式),展示技术深度。