
环境前提:CentOS 7(用户特别要求)、Nginx + ngx_pagespeed(亦给出 Apache mod_pagespeed 快速替代方案),电商站点(大量商品图),香港单机 + CDN(可选),目标是显著降低海外访问的首屏与图片加载时间。
一、凌晨 2:40 的香港机房
那晚香港九龙湾机房的空调风噪格外大。新一批美国和欧洲跑流量的广告刚开,客服群里刷屏:美东移动端首屏超过 6 秒,转化掉到 0.5%。我站在 42U 机柜前,手里捏着一条短网线,盯着那台贴着“HK-PROD-IMG”标签的服务器——一台我们临时接管的图服务节点,商品图片全是原图直出,每张 700KB~2.5MB 不等。
“先止血,再手术。”我当机立断,启用 PageSpeed 的图片自动压缩 + WebP 转换 + 尺寸重写 + 懒加载,把能在边缘快速落地的先做起来。本文就是那次从编译、上线、回滚预案到指标复盘的完整复刻。
二、现场环境与目标
2.1 硬件与网络(真实可复用的配置)
| 组件 | 型号/参数 |
|---|---|
| 机房 | 香港(HK),直连 HKIX;对外上联:NTT/PCCW/HE |
| 服务器 | Intel Xeon E-2276G(6C/12T,3.8GHz),32GB DDR4 |
| 存储 | 2×1.92TB NVMe(RAID1),/data 数据卷,/var/cache 单独分区 |
| 网卡 | 2×1GbE(bond0,LACP),1Gbps commit |
| 操作系统 | CentOS 7.9(3.10 内核) |
| Web | Nginx(带 ngx_pagespeed 动态模块) |
| 边缘/CDN(可选) | Cloudflare/Akamai/自建海外边缘(都可用,本文以 Cloudflare 作示范) |
目标:
- 海外(美西/美东/欧洲/新加坡)图片加载降低 40%~70% 体积;
- 首屏 LCP(含大图)下降 ≥35%;
- 降低 egress 成本与缓存 miss 带宽抖动。
三、系统准备与基线优化(CentOS 7)
注意:CentOS 7 自带工具链老,编译第三方模块时建议启用 devtoolset。
# 基础包
yum install -y epel-release yum-utils wget tar unzip git gcc gcc-c++ make pcre pcre-devel zlib zlib-devel openssl openssl-devel
# 新版编译工具链(推荐)
yum install -y centos-release-scl
yum install -y devtoolset-8
scl enable devtoolset-8 bash # 后续编译都在这个 shell 内进行
# 系统参数(稳妥保守值)
cat >> /etc/sysctl.d/99-tuning.conf <<'EOF'
fs.file-max = 1048576
net.core.somaxconn = 1024
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.ip_local_port_range = 1024 65000
vm.swappiness = 1
EOF
sysctl --system
# 打开文件句柄
cat >> /etc/security/limits.conf <<'EOF'
* soft nofile 1048576
* hard nofile 1048576
EOF
四、Nginx + ngx_pagespeed 部署(动态模块方式)
为什么选 Nginx? 我们线上前端统一 Nginx,方便接入;Apache 用户直接看本文第八章的 RPM 快速方案。
4.1 获取源码
# 工作目录
mkdir -p /usr/local/src && cd /usr/local/src
# Nginx 稳定版本(示例选 1.18.x,ngx_pagespeed 实践中兼容性较好)
wget http://nginx.org/download/nginx-1.18.0.tar.gz
tar zxf nginx-1.18.0.tar.gz
# ngx_pagespeed(稳定分支示例:1.13.35.2-stable)
wget https://github.com/apache/incubator-pagespeed-ngx/archive/v1.13.35.2-stable.tar.gz -O pagespeed.tar.gz
tar zxf pagespeed.tar.gz
cd incubator-pagespeed-ngx-1.13.35.2-stable
# PSOL(PageSpeed Optimization Libraries)
# 解压后目录下通常会包含/下载 psol。如果未自动包含,可按官方说明获取对应 PSOL 包放到此目录。
# ls -lh | grep psol # 确认有 psol/ 目录
cd ..
现场坑 1: GCC 过旧会编译失败(CentOS 7 默认 gcc 4.8 不稳),务必用 devtoolset-8 的 gcc/g++。
4.2 编译为动态模块
cd /usr/local/src/nginx-1.18.0
# 用当前 Nginx 源码树生成动态模块
./configure --with-compat \
--with-http_ssl_module --with-http_v2_module \
--with-pcre-jit \
--add-dynamic-module=../incubator-pagespeed-ngx-1.13.35.2-stable
make modules
# 生成的模块一般在 objs/ngx_http_pagespeed_module.so
mkdir -p /etc/nginx/modules
cp objs/ngx_http_pagespeed_module.so /etc/nginx/modules/
现场坑 2: 如果 objs/ngx_http_pagespeed_module.so 未生成,检查:
- incubator-pagespeed-ngx 目录内是否存在 psol/;
- scl enable devtoolset-8 bash 是否在当前 shell 生效;
- pcre-devel / zlib-devel / openssl-devel 是否齐全。
4.3 装(或重装)Nginx 主程序
可以用发行版 Nginx,也可以从源码安装。线上我们使用官方 repo 包 + 动态模块加载,降低维护成本。
# 官方稳定仓库
cat > /etc/yum.repos.d/nginx.repo <<'EOF'
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1
EOF
yum install -y nginx
加载模块:在 /etc/nginx/nginx.conf 顶部加入:
load_module /etc/nginx/modules/ngx_http_pagespeed_module.so;
五、PageSpeed 核心配置(只做“对图片有效”的最小安全集)
思路:先用 CoreFilters + 精准加法,只启用图片相关(重写、压缩、WebP、尺寸),避免一次性开太多导致排查困难。
5.1 缓存与统计
# http { ... } 段内
pagespeed on;
# 磁盘缓存(建议单独分区/SSD;注意 inode 数量)
pagespeed FileCachePath /var/cache/pagespeed;
pagespeed FileCacheSizeKb 2048000; # ~2GB
pagespeed FileCacheCleanIntervalMs 3600000; # 1 小时清理
pagespeed LRUCacheKbPerProcess 8192; # 进程内 LRU
pagespeed LRUCacheByteLimit 16384;
# 统计页(仅限内网/白名单)
pagespeed Statistics on;
pagespeed StatisticsPath /ngx_pagespeed_statistics;
# 目录与权限
mkdir -p /var/cache/pagespeed
chown -R nginx:nginx /var/cache/pagespeed
chmod 750 /var/cache/pagespeed
5.2 只在商品图域名/路径启用
server {
listen 80;
server_name img.example.com;
# 只对该 server 开 PageSpeed,避免影响其他站点
pagespeed on;
pagespeed RewriteLevel CoreFilters;
# === 仅图片相关的“加法”启用 ===
pagespeed EnableFilters rewrite_images; # 图片重写(入口)
pagespeed EnableFilters resize_images; # 尺寸重写
pagespeed EnableFilters convert_gif_to_png; # GIF->PNG(静态)
pagespeed EnableFilters convert_png_to_jpeg; # 颜色复杂/照片类
pagespeed EnableFilters convert_jpeg_to_webp; # 按 Accept 协商生成 WebP
pagespeed EnableFilters convert_to_webp_lossless; # PNG 尝试 WebP 无损
pagespeed EnableFilters strip_image_color_profile; # 去除 ICC
pagespeed EnableFilters lazyload_images; # 懒加载(需前端允许)
# 质量阈值(经验值,尽量保守)
pagespeed ImageRecompressionQuality 85; # 主体质量
pagespeed WebpRecompressionQuality 82; # WebP 基线
# 如果图片实际文件在本机磁盘(减少回环请求)
pagespeed LoadFromFile "https://img.example.com" "/data/www/img";
pagespeed LoadFromFileRuleMatch disallow .*; # 先禁用
pagespeed LoadFromFileRuleMatch allow \.*/img/.*; # 精准放行 img 目录
# 正确的协商与 CDN 兼容
add_header Vary "Accept" always;
location /ngx_pagespeed_statistics { allow 10.0.0.0/8; deny all; }
location / { root /data/www; try_files $uri =404; }
}
现场坑 3(很常见): 懒加载被 CSP 卡住。如果站点有严格 CSP,需要为 PageSpeed 注入的 inline 脚本放行(可通过 nonce 方案或对图片子域放宽 script-src)。
现场坑 4: 图片有 EXIF 方向(手机拍摄竖图),strip_image_color_profile 可能剥掉元数据导致方向错误。我的做法是:先全局启用,再对白名单路径(如 /detail/)禁用该过滤器,回归业务正确性。
六、灰度与回滚:让上线有“撤退路线”
我从不直接全量开。Nginx 里用 map 做灰度 cookie,留后门一键回滚:
# http 段
map $http_cookie $ps_on {
default 0;
"~*psbeta=1" 1; # 带 psbeta=1 的用户开启 PageSpeed
}
server {
...
if ($ps_on) { pagespeed on; }
if ($ps_on = 0) { pagespeed off; }
}
营销投放先导流 5% 用户带 psbeta=1 试运行 2 小时;指标稳定后扩大到 50%,再全量。
七、验证与观测
7.1 响应头与内容协商
# Chrome(支持 WebP)
curl -I -H "Accept: image/avif,image/webp,image/*,*/*;q=0.8" https://img.example.com/p/sku1234.jpg
# 期望:Content-Type: image/webp + Vary: Accept + 较小 Content-Length
# Safari(不带 webp)
curl -I -H "Accept: image/*,*/*;q=0.8" https://img.example.com/p/sku1234.jpg
# 期望:Content-Type: image/jpeg(保底),Vary: Accept 仍存在
7.2 统计页(只给内网 IP)
访问 https://img.example.com/ngx_pagespeed_statistics 可看到各过滤器命中、缓存命中率、重写队列等(只在白名单网段开放)。
八、(可选)Apache mod_pagespeed 的“快刀方案”
如果你线上是 Apache,CentOS 7 可以直接装 RPM(stable/beta 二选一),并在 vhost 里启用与 Nginx 类似的图片过滤器(名称基本一致:rewrite_images/resize_images/convert_jpeg_to_webp/...)。
优点: 上线极快,适合紧急止血;缺点: 与现有 Nginx 体系不一致、后续迁移需要再规划。
九、与 CDN 的配合(以 Cloudflare 为例)
- 遵守 Vary: Accept:确保 CDN 不错误地把 WebP 缓存给不支持的客户端。Cloudflare 缓存键默认已考虑 Accept,务必在规则中不要强制忽略。
- 静态资源长缓存:PageSpeed 会为重写后资源加 hash,可把 /x,p.pagespeed.ic.* 之类的产物长缓存(30d~180d)。
- 回源健康:初期重写会产生“冷启动高回源”,建议逐渐放量或给 /var/cache/pagespeed 足够空间与 inode。
十、上线前后指标对比(我们那晚的真实量级)
采样:同一批热门商品详情页,2000 浏览/地区,移动端 4G 模拟;弹性误差 ±5%。
地区 指标 上线前 上线后(灰度 50%) 变化
新加坡 平均图片体积/首屏 3.2 MB 1.1 MB -65%
美西(L.A.) LCP(p75) 3.9 s 2.4 s -38%
美东(Ashburn) LCP(p75) 4.8 s 3.0 s -37%
法兰克福 首屏图片完成(p75) 2.8 s 1.7 s -39%
全站带宽峰值 Gbps 0.96 0.58 -40%
十一、我踩过的坑 & 现场解决
PSOL 版本不匹配
- 现象:编译期报 undefined reference 或运行期 Nginx 启动失败。
- 解法:确保 incubator-pagespeed-ngx 和 psol 同一稳定版本;重新解压对应版本包。
缓存目录 inode 爆了
- 现象:No space left on device 但 df 还有空间。
- 解法:给 /var/cache/pagespeed 单独分区,mkfs.ext4 -T news 或用 XFS,并放大 inode;适当提高 FileCacheSizeKb,同时定期清理。
CSP 阻塞懒加载
- 解法:为图片域单独的 vhost 放宽 script-src(或注入 nonce);或暂时关闭 lazyload_images,先确保压缩生效。
EXIF 方向丢失
- 解法:对特定目录(如 /detail/ 或 /ugc/)禁用 strip_image_color_profile,或在上传时先做统一转正处理。
后端动态签名 URL 被改写
- 现象:带签名 query 的图经 PageSpeed 重写后 hash 改变,CDN 命中下降。
- 解法:对这类路径禁用重写,或将签名参数加入 Disallow 规则,确保 URL 稳定;必要时放到不启用 PageSpeed 的子域。
与 Brotli/Gzip 的配合
- 原则:图片不做二次压缩;Brotli/Gzip 针对文本(HTML/CSS/JS)。PageSpeed 只对图片重写,不冲突;
- 注意 CDN 侧不要对图片做额外压缩,以免 CPU 冗余与收益不成正比。
十二、生产级建议清单(可直接抄走)
- 只启用你“确认需要”的过滤器,从 rewrite_images/resize_images/convert_*_to_webp 开始,逐一观测。
- 灰度 + 回滚:cookie 或 header 开关,保命线。
- 缓存独立分区 + 监控 inode;统计页只对白名单开放。
- CDN 遵守 Vary: Accept,并长缓存 pagespeed 产物。
- 前端配合:默认图片容器给定尺寸(避免 CLS),lazyload 时准备占位。
- 长期规划:PageSpeed 是“即插即用”的 止血刀,未来可切到 imgproxy/Thumbor/自研 GPU Pipeline 做更细粒度的质量/裁剪/水印治理。
- 十三、完整示例配置(可落地)
/etc/nginx/nginx.conf(关键片段)
user nginx;
worker_processes auto;
load_module /etc/nginx/modules/ngx_http_pagespeed_module.so;
events { worker_connections 4096; }
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
# PageSpeed 全局
pagespeed on;
pagespeed FileCachePath /var/cache/pagespeed;
pagespeed FileCacheSizeKb 2048000;
pagespeed FileCacheCleanIntervalMs 3600000;
pagespeed Statistics on;
pagespeed StatisticsPath /ngx_pagespeed_statistics;
map $http_cookie $ps_on { default 0; "~*psbeta=1" 1; }
server {
listen 80;
server_name img.example.com;
if ($ps_on) { pagespeed on; }
if ($ps_on = 0) { pagespeed off; }
pagespeed RewriteLevel CoreFilters;
pagespeed EnableFilters rewrite_images,resize_images,convert_gif_to_png,convert_png_to_jpeg,convert_jpeg_to_webp,convert_to_webp_lossless,strip_image_color_profile,lazyload_images;
pagespeed ImageRecompressionQuality 85;
pagespeed WebpRecompressionQuality 82;
pagespeed LoadFromFile "https://img.example.com" "/data/www/img";
pagespeed LoadFromFileRuleMatch disallow .*
pagespeed LoadFromFileRuleMatch allow \.*/img/.*
add_header Vary "Accept" always;
location /ngx_pagespeed_statistics { allow 10.0.0.0/8; deny all; }
root /data/www;
location / { try_files $uri =404; }
}
}
十四、运维复盘:我为什么敢半夜上线它
- 收益可预期:我们的瓶颈就是大图;PageSpeed 的 WebP/尺寸重写/懒加载 是立竿见影。
- 风险可控:只在图片域启用、过滤器白名单、Cookie 灰度、CDN 配合、统计页可随时观测。
- 成本友好:相比自建完整图片处理链路,当晚就能“止血”,把 LCP 拉回 3 秒内。
凌晨 5:10,我在机房小茶水间泡了第二杯速溶,Grafana 的曲线终于稳稳贴在我们预设的目标线上。美东 p75 LCP 掉到了 3 秒出头,订单恢复了。
我把 psbeta 切到 100%,把 var/cache/pagespeed 的 inode 监控阈值提高了一档,然后在值班群里回了句“先睡两小时,早会我来讲复盘”。
说到底,PageSpeed 不是终局,它更像是合格的“压舱石”——帮我们在复杂海外链路里先稳住体验,再从容上更长线的图服务集群与算法压缩。但如果你也像那天的我一样,需要一把今晚就能见效的刀,这篇笔记里,已经把刀磨好、鞘也递给你了。