Web 性能优化实战:HTTP/2 开启、gzip/brotli 压缩与浏览器缓存策略


Web 性能优化实战:HTTP/2 开启、gzip/brotli 压缩与浏览器缓存策略

Web 性能优化实战:HTTP/2 开启、gzip/brotli 压缩与浏览器缓存策略

很多站长买了一台不错的 VPS,带宽也不小,但打开网站就是觉得慢。升级配置之前,先想想流量到底浪费在哪里:同一张页面可能包含几十个 JS/CSS/图片请求,每个请求都要重新走一遍 TCP 握手、TLS 握手、HTTP 请求响应,还要重复传输大量文本字节。本文从传输层入手,讲清楚三个性价比最高的优化动作:开启 HTTP/2、压缩文本响应、设置浏览器缓存,全程给出可复制的配置和排错命令。

一、先测量再优化:找出页面慢的环节

优化的前提是量化。不要凭感觉调参,先用 curl 把一条请求的耗时拆开看。下面这条命令能输出请求的状态码、总耗时、TTFB(首字节时间,反映服务端处理速度)和下载字节数:

curl -o /dev/null -s -w "状态码:%{http_code} 总耗时:%{time_total}s TTFB:%{time_starttransfer}s 下载:%{size_download}B 速度:%{speed_download}B/s\n" https://www.example.com/

常见的三种慢:

  • TTFB 很高:瓶颈在服务端(PHP 执行慢、数据库慢),压缩和 HTTP/2 帮不上忙,要查后端。
  • TTFB 快但总耗时很长:瓶颈在传输,主要看体积和连接数,本文的方案正好对症。
  • 单个请求都很快,但整页很慢:请求数量太多,浏览器串行排队,开启 HTTP/2 多路复用收益最大。

建议优化前先记录每个静态资源的字节数和请求数,优化后再对比,用数据说话。

二、开启 HTTP/2:多路复用消除排队延迟

HTTP/1.1 下浏览器对一个域名通常只能建立 6 个并发连接,资源多了就得排队;HTTP/2 在一个连接上多路复用所有请求,还带头部压缩(HPACK),对几十个静态资源的页面提升非常明显。开启它的前提是:必须使用 HTTPS,且 Nginx 版本不低于 1.9.5、OpenSSL 不低于 1.0.2。

先确认你的 Nginx 是否编译了 http_v2 模块:

nginx -V 2>&1 | grep -o "http_v2_module\|http_ssl_module" | sort -u
# 输出同时包含 http_v2_module 和 http_ssl_module 才说明具备条件

然后修改站点配置文件,监听指令加上 http2:

server {
    listen 443 ssl http2;
    server_name www.example.com;

    ssl_certificate     /etc/nginx/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;
}

保存后校验并重载:

nginx -t && systemctl reload nginx

排错:怎么确认 HTTP/2 真的生效了?用 curl 直接探测协议版本:

curl -sI --http2 -o /dev/null -w "协商协议:%{http_version}\n" https://www.example.com/
# 输出 "协商协议:2" 表示成功;输出 1.1 则未生效

如果输出仍是 1.1,依次检查:证书是否有效(HTTP/2 强制 TLS);TLS 是否低于 1.2;服务器是否在 443 端口正常监听(ss -lntp | grep 443);是否存在 CDN 或前置反代不支持 ALPN(HTTP/2 需要 TLS ALPN 扩展协商)。

三、gzip 压缩:文本资源立省 60% 流量

HTML、CSS、JS、JSON、SVG 这类文本资源有极高的重复度,gzip 后通常能减少 60%~80% 的体积。Nginx 默认已开 gzip,但很多发行版默认只压缩 html 类型,必须显式补齐 mime 类型。推荐配置:

gzip on;
gzip_vary on;
gzip_comp_level 5;
gzip_min_length 1k;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/javascript application/xml application/xhtml+xml image/svg+xml application/x-javascript text/xml; 
# gzip_disable "MSIE [1-6]\.";

要点:

  • gzip_comp_level 5 是甜点:1~3 压缩率太低,6~9 会明显消耗 CPU,对 VPS 来说 5 的性价比最高。
  • gzip_min_length 1k:小于 1KB 的资源压缩后反而更大,不要压。
  • 不要压缩图片和视频:JPEG/PNG/WebP/MP4 本身就是压缩格式,再 gzip 只会白白消耗 CPU。

保存后同样 nginx -t && systemctl reload nginx。用 curl 模拟带 Accept-Encoding 头的浏览器请求来验证:

curl -sI -H "Accept-Encoding: gzip" https://www.example.com/ | grep -i "content-encoding\|content-type"
# 应看到: content-encoding: gzip

排错:为什么没有 content-encoding: gzip?按顺序排查:

  1. 响应的 content-type 是否在 gzip_types 列表里(API 返回 application/json 但列表漏了最常见);
  2. 响应体是否小于 gzip_min_length;
  3. 是否经过 CDN 或反代把编码头吞掉了;
  4. 后端是否已经压缩过一次——如果 PHP-FPM 层已经输出 Content-Encoding: gzip,Nginx 默认不再二次压缩,此时要改在后端关掉压缩,统一交给 Nginx 做。

四、brotli 压缩:比 gzip 再省 20%

Brotli 是 Google 推出的新一代压缩算法,相同压缩级别下比 gzip 体积再小 15%~25%,主流浏览器早已全量支持。缺点是需要额外安装模块,主流发行版可以通过第三方源或源码编译安装 ngx_brotli:

# 以 Debian/Ubuntu 使用 nginx-extras 或源码编译为例
git clone https://github.com/google/ngx_brotli /tmp/ngx_brotli
cd /tmp/ngx_brotli
git submodule update --init

# 若用源码编译 nginx:
./configure --with-compat --add-dynamic-module=../ngx_brotli
make modules
cp objs/ngx_http_brotli_filter_module.so /etc/nginx/modules/
echo "load_module modules/ngx_http_brotli_filter_module.so;" > /etc/nginx/modules-enabled/50-brotli.conf

加载模块后,在 http 块里开启:

brotli on;
brotli_comp_level 6;
brotli_min_length 1k;
brotli_types text/plain text/css application/json application/javascript text/javascript image/svg+xml application/xml;

Brotli 压缩级别 6 与 gzip 5 的 CPU 开销相当,但体积更小。需要说明:Nginx 的 brotli 和 gzip 可以同时开启,支持 brotli 的浏览器会自动协商使用 brotli,老浏览器回退到 gzip,互不影响。

验证方法与 gzip 相同,把 Accept-Encoding 换成 br:

curl -sI -H "Accept-Encoding: br" https://www.example.com/ | grep -i content-encoding
# 应看到: content-encoding: br

若返回 404 或 502,先确认模块加载成功:nginx -t 是否报 module 相关错误,以及 nginx -V 2>&1 | tr ' ' '\n' | grep brotli 是否能看到模块名。

五、浏览器缓存:让静态资源只传一次

压缩解决"传得少",缓存解决"不用再传"。给不经常变化的静态资源加上长缓存头,用户第二次访问时直接从本地缓存读取,请求根本不会到达服务器。在 Nginx 里针对静态目录配置:

location ~* \.(js|css|png|jpe?g|gif|webp|svg|ico|woff2?)$ {
    expires 30d;
    add_header Cache-Control "public, max-age=2592000, immutable";
    access_log off;
    log_not_found off;
    try_files $uri =404;
}

几个关键点:

  • expires 30d 与 Cache-Control 二选一即可,Nginx 的 expires 会自动生成 Cache-Control,这里显式写出来方便调试。
  • immutable 提示浏览器:只要 URL 不变就永远不重新验证,适合配合"文件名带内容 hash"的构建产物(如 app.8f3a2b.js)。
  • index.html 和 API 响应千万别加长缓存,否则用户永远看到旧页面;给 HTML 加 no-cache 或极短缓存:
location = /index.html {
    add_header Cache-Control "no-cache, must-revalidate";
}

验证缓存头:

curl -sI https://www.example.com/static/app.js | grep -i "cache-control\|etag\|last-modified"
# 应看到: cache-control: public, max-age=2592000, immutable

排错:缓存不生效?检查请求是否落在静态 location 匹配范围内(正则 location 的优先级);检查有没有别的 location 用 add_header 覆盖;还要确认 Nginx 开启 gzip/brotli 后返回的 Vary 头(gzip_vary on)是否正常,否则代理缓存可能把压缩过的内容错误地提供给不支持的客户端。

六、图片优化:页面体积的头号大户

优化完文本资源后,图片往往是剩余流量的大头。VPS 场景下建议做三件事:

  1. 转 WebP/AVIF 格式:同样画质比 JPEG 小 25%~50%,可以用 cwebp 批量转换:
cwebp -q 80 input.jpg -o output.webp
# 配合 Nginx 的 map 按 Accept 头自动切图:
map $http_accept $webp_suffix {
    default   "";
    "~*webp"  ".webp";
}
# location 里: try_files $uri$webp_suffix $uri =404;
  1. 懒加载:页面首屏外的图片用 loading="lazy" 延迟加载。
  2. 正确尺寸:不要让浏览器下载 2000px 原图再缩放到 400px 展示,先压到展示尺寸再上传。

七、优化效果验证与整体检查清单

优化结束后,回到第一节的测量命令逐条对比。一个完整的验证流程:

# 1. 检查协议版本
curl -sI --http2 -o /dev/null -w "协议:%{http_version}\n" https://www.example.com/

# 2. 检查 gzip 与 br 是否生效
curl -sI -H "Accept-Encoding: gzip" https://www.example.com/ | grep -i content-encoding
curl -sI -H "Accept-Encoding: br"    https://www.example.com/ | grep -i content-encoding

# 3. 对比压缩前后的下载体积
curl -s https://www.example.com/ -o /dev/null -w "无压缩:%{size_download}B\n"
curl -s --compressed https://www.example.com/ -o /dev/null -w "压缩后:%{size_download}B\n"

# 4. 确认缓存头
curl -sI https://www.example.com/static/app.js | grep -i "cache-control"

另外建议用 Chrome DevTools 的 Network 面板配合"Slow 3G 模拟"做一次真实用户视角的加载测试,看瀑布图里还有哪些请求在排队、哪些资源没命中缓存。性能优化是持续过程,每次发布新版本后都跑一遍上面的检查,比出事后再救火省心得多。

八、常见问题速查

现象原因解决
curl 显示协议 1.1,HTTP/2 未生效TLS 版本低于 1.2、证书无效、前置反代不支持 ALPN确认 ssl_protocols 含 TLSv1.2/1.3,证书链完整,检查 CDN 配置
gzip 不生效content-type 未列入 gzip_types、体积小于 min_length、后端已压缩补齐类型列表,减小阈值,后端关闭压缩统一由 Nginx 处理
brotli 404/502模块未加载、编译时未启用nginx -t 检查模块加载行,重编或换含 brotli 的发行版包
静态资源不缓存location 匹配优先级、add_header 被覆盖、Cache-Control 缺失用正则 location 匹配扩展名,检查是否有其他 add_header 覆盖
压缩后体积反而更大小文件被压缩、图片/视频被 gzip调大 gzip_min_length,gzip_types 不要包含图片格式

本文覆盖的配置都在 Nginx 层面完成,改动小、可回滚(备份配置文件后 nginx -t 校验再 reload),对任何 LNMP 架构的 VPS 都适用。把测量、压缩、缓存这三板斧练好,大多数静态资源型页面的加载速度都能有明显改善。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

Nginx 强制 HTTPS 跳转实战:301/302 选型、HSTS 配置与常见排错

VPS 监控搭建实战:Prometheus + node_exporter + Grafana 从零部署到告警通知

评 论
更换验证码