Node 20 上给 Express 接口加 gzip,我在压缩阈值上翻过车
This post is available in 简体中文 only — the original follows.
项目是 Node 20.11 + Express 4,一个内部查询接口,返回的商品列表 JSON 平均 40 到 80KB,峰值 2MB 左右。上线后发现客户端首屏慢,抓包看到响应没压缩,于是加 compression。第一版图省事,直接默认参数:
const compression = require('compression');
app.use(compression());
结果 CPU 监控在高峰时段跳了 8 到 10 个百分点,小接口的 P99 反而变差。后来把阈值和类型卡紧,才稳下来:
app.use(compression({
threshold: 1024, // 小于 1KB 不压
level: 4, // 默认 6,降到 4
filter: (req, res) => {
if (req.headers['x-no-compression']) return false;
return compression.filter(req, res);
}
}));
另外在网关侧也设了 Vary: Accept-Encoding,避免 CDN 把压缩和非压缩版本混着缓存。
为什么这样做:gzip 不是免费的。8KB 以下的响应,压缩本身的开销加上客户端解压,经常抵不过省下的那点字节;level: 6 是默认值,对 JSON 这种重复度高的文本,4 和 6 的体积差通常只有几个百分点,CPU 却差不少。我实测一个 60KB 的列表响应,level 4 压到 11.2KB,level 6 压到 10.8KB,差距可以忽略。代价是压缩级别调低后,特别大的响应体积会略大,需要看自己的带宽成本。
哪里会失效:一是已经压缩过的资源,比如图片、字体、预压缩的 .gz 文件,再压一遍纯浪费,filter 里要按 Content-Type 排掉;二是 SSE 或长轮询接口,流式输出经过 gzip 缓冲,客户端会收不到实时数据,这类路由得单独跳过;三是我踩过的坑:某个接口手动设了 Content-Length,压缩后长度变了但头没更新,客户端直接截断报错,后来改成不手写这个头。还有反向代理已经压过的情况,重复压缩会出乱码,排查时可以先看 Content-Encoding 是否已经存在。
你们的接口在加压缩后,是看 P99 还是看带宽账单来决定阈值的?我这边两个指标经常打架。
0 comments
No comments yet — be the first.