从源码编译 nginx 1.25.0 + PageSpeed

为什么又回头折腾单体性能

云和微服务把应用拆得越来越细,到了某个时间点,几乎没人再关心 HTTP/1.0、HTTP/1.1 时代的 C10K、C100K 了。性能不够就加机器——尤其是上了云和 k8s 之后,没有直接的硬件投入,起个服务 API 调一下、鼠标点一点,几百上千台虚拟机或 Pod 就拉起来了,容器动辄几万几十万个。在"人比机器贵"的年代,这点成本确实不显眼。

但账单是会累积的。疫情之后大家口袋都紧了,回头一看才发现,不知不觉在云上已经烧掉了这么多钱。鼓吹下云也好、继续上云也罢,理念不重要,重要的是经济账。于是单体应用的性能,理应重新被认真对待。

在 Web 加速这件事上,Google 和 nginx 曾经各领风骚。PageSpeed + nginx 的组合一度成就了被 Google 以 5 亿美金收购的 Yottaa 这类公司——对 Web 应用做页面精简、HTML/CSS 压缩、动态加速。可这套东西大概从 2018 年之后就突然没人提了。

原因不难理解。现代前端框架在 build 阶段自己就做了各种裁剪优化,甚至倾向于 SSR(服务端渲染)——很多时候是因为纯前端框架对搜索引擎太不友好,搞出来的页面搜索引擎根本收录不了,没有收录就没有流量,没有流量就没有业务。于是渲染的活儿又被推回了服务端。可服务器的算力始终是有限的,而且是要付费的;客户端的算力却随用户数增长、而且免费。把能在用户侧做的事情塞回服务器,本身就是笔赔本买卖。

所以 PageSpeed 这类在服务端做页面优化的方案,今天确实算不上主流。它值得做的场景很窄:你跑的是传统的、服务端渲染的单体站点,前端没有现代化构建链路,又实打实需要压缩 HTML、内联关键 CSS、合并资源,而你不想、也没条件去重写前端。如果你的站点已经是现代 SPA/SSR 架构,这篇可以跳过。

网上会有人让你直接拉别人打包好的 Docker 镜像或 deb 包。我们选择自己从源码编译——版本、模块、链接方式都捏在自己手里,出了问题也知道从哪查。下面是我们在生产环境里实际跑通的步骤。

环境与版本

OS:      CentOS 7.9
GCC:     4.8.5
NGINX:   1.25.0
PAGESPEED (NPS): 1.13.35.2

PageSpeed 需要把一大堆图像、音视频、数学运算相关的开发库链进来,所以依赖列表比普通 nginx 编译长得多。完整清单如下:

包名用途
wget用于从网络下载文件
gccC/C++ 编译器
gcc-c++C++ 编译器
zlib-develzlib 开发库
libcurl-devellibcurl 开发库
openssl-develOpenSSL 开发库
bzip2-develbzip2 开发库
gmp-devel.x86_64GNU Multiprecision Library 开发库(x86_64)
mpfr-devel.x86_64MPFR Library 开发库(x86_64)
libmpc-devel.x86_64MPC Library 开发库(x86_64)
libSM-devel.x86_64X.Org SM 库开发库(x86_64)
libxml2-devellibxml2 库开发库
gcc-gfortran.x86_64Fortran 编译器(x86_64)
libjpeg-turbo-devel.x86_64libjpeg-turbo 库开发库(x86_64)
openjpeg-develOpenJPEG 库开发库
libpng12-devellibpng 库开发库
libtiff-devellibtiff 库开发库
cmake3.x86_64CMake 3 编译系统(x86_64)
libtools-devellibtools 开发库
yum-utilsyum 实用工具
libffi-devellibffi 库开发库
jemalloc-devel.x86_64jemalloc 内存分配器开发库(x86_64)
jasper-develJasPer 库开发库
tbb-develThreading Building Blocks 库开发库
libdc1394-devellibdc1394 库开发库
gstreamer-devel.x86_64GStreamer 库开发库(x86_64)
blosc-develBlosc 库开发库
libaec-devellibaec 库开发库
brotli-develBrotli 库开发库
giflib-develgiflib 库开发库
gtk2-develGTK+ 2 库开发库
libaio-devellibaio 库开发库
libsqlite3x-devel.x86_64libsqlite3x 库开发库(x86_64)
make构建工具
eigen3-develEigen3 线性代数库开发库

安装

安装依赖

yum install epel-release.noarch -y
yum install wget gcc gcc-c++ zlib-devel libcurl-devel openssl-devel bzip2-devel gmp-devel.x86_64 mpfr-devel.x86_64 libmpc-devel.x86_64 libSM-devel.x86_64 libxml2-devel gcc-gfortran.x86_64 libjpeg-turbo-devel.x86_64 openjpeg-devel.x86_64 libpng12-devel.x86_64 libtiff-devel cmake3.x86_64 libtool-devel yum-utils libffi-devel jemalloc-devel.x86_64 jasper-devel.x86_64 tbb-devel libdc1394-devel gstreamer-devel.x86_64 blosc-devel libaec-devel brotli-devel.x86_64 giflib-devel.x86_64 gtk2-devel libaio-devel libsqlite3x-devel.x86_64 make eigen3-devel libpcre3-devel

下载源码并解压

wget -O nps.zip https://github.com/apache/incubator-pagespeed-ngx/archive/71e24c1c47113acb5924d8cb523d572b376e9dd0.zip && unzip nps
wget http://nginx.org/download/nginx-1.25.0.tar.gz
wget -O psol.tar.gz https://dl.google.com/dl/page-speed/psol/1.13.35.2-x64.tar.gz

PageSpeed 的 nginx 模块(NPS)和它依赖的预编译库 PSOL 是分开的两部分:前者是源码,后者是 Google 发布的二进制 PSOL(PageSpeed Optimization Library),必须和 NPS 版本对应。这里的 commit 71e24c1c47113acb5924d8cb523d572b376e9dd0 就是 NPS 1.13.35.2 对应的代码。

编译前的关键一步:LD_LIBRARY_PATH

PSOL 的库文件解压后在 PageSpeed 模块目录的 usr/lib 下。nginx 在 configure 和编译 PageSpeed 模块时需要能找到它们,所以要显式把 LD_LIBRARY_PATH 指向那里:

LD_LIBRARY_PATH=../incubator-pagespeed-ngx-${PAGESPEED_VERSION}/usr/lib:/usr/lib

其中 incubator-pagespeed-ngx-71e24c1c47113acb5924d8cb523d572b376e9dd0 就是上一步解压出来的、对应版本的 PageSpeed 源码目录。这一步漏了,编译会直接失败。

编译安装

LD_LIBRARY_PATH=../incubator-pagespeed-ngx-${PAGESPEED_VERSION}/usr/lib:/usr/lib ./configure --sbin-path=/usr/sbin --modules-path=/usr/lib/nginx  --with-http_ssl_module --with-http_gzip_static_module --with-file-aio --with-http_v2_module --with-http_realip_module --with-http_sub_module --with-http_gunzip_module --with-http_secure_link_module --with-http_stub_status_module --with-threads --with-stream --with-stream_ssl_module  --without-http_autoindex_module --without-http_browser_module --without-http_userid_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --without-http_split_clients_module --without-http_uwsgi_module --without-http_scgi_module --without-http_upstream_ip_hash_module --prefix=/etc/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --pid-path=/var/run/nginx.pid --add-module=../ngx_devel_kit --add-module=../ngx_cache_purge --add-module=../nginx-module-vts --add-module=../echo-nginx-module --add-module=../srcache-nginx-module --add-module=../set-misc-nginx-module  --add-module=../headers-more-nginx-module --add-module=../ngx_http_substitutions_filter_module --add-module=../incubator-pagespeed-ngx-71e24c1c47113acb5924d8cb523d572b376e9dd0

这条 configure 里我们做了几类取舍。开的模块是生产上真正要用的:http_ssl / http_v2 走 HTTPS 和 HTTP/2,realip 还原客户端真实 IP(我们前面通常有 LB 或 CDN),gzip_static / gunzip 配合预压缩,threads 和 file-aio 让磁盘 I/O 不阻塞 worker,stream / stream_ssl 用来做四层代理,stub_status / vts(nginx-module-vts)出监控指标。

关掉的都是我们用不到、又徒增攻击面和体积的:autoindex(不做目录列表)、mail 的 pop3/imap/smtp(这台不是邮件代理)、uwsgi / scgi(只用 fastcgi 和反代)、browser / userid / split_clients 等。能 --without 的就关掉,编出来的二进制更干净。

末尾一串 --add-module 是第三方模块:ngx_devel_kit 和 set-misc 是很多模块的基础,cache_purge 用于主动清缓存,echo / srcache 配合缓存逻辑,headers-more 改响应头,substitutions_filter 做响应体替换,最后才是 PageSpeed 本体。

configure 通过之后:

make
make install

装完后 nginx 的配置文件在 /etc/nginx 下。

小结

真正的坑只有一个:PSOL 的 LD_LIBRARY_PATH 要指对,否则 PageSpeed 模块编不过。其余就是依赖装齐、configure 的开关按自己的用途取舍。

最后给个判断标准:如果你的站点是传统服务端渲染、前端没有现代构建链路,PageSpeed 还能省掉一轮人工优化;如果已经是 SPA/SSR 架构,这套东西不值得上,把算力留在用户侧更划算。具体的 PageSpeed filter 配置,我们下一篇再展开。

Published At (Updated At) Tagged with