Node.js 压缩与 zlib:gzip、brotli 与流式管线
Stream 篇说过大文件要走流;压缩场景里如果 先读全再压,内存照样炸。
zlib(以及brotli等)在 Node 里通常以 Transform 流 或 Promise 版一次性 API 出现。
这一篇想讲清楚几件事:什么时候在 Node 里做压缩、流式管线怎么接、以及和反向代理 gzip 的分工。
为什么在应用里还会碰 zlib
常见路径:
- HTTP 响应里
Content-Encoding: gzip(不少框架或中间件帮你做); - 落盘归档、日志打包、大 JSON 上传前压缩;
- 与对象存储 / CDN 对接时,源站或构建步骤里 先压好再传。
另一层现实:
- Nginx / CDN 往往已经 对文本类资源做 gzip/br,Node 里再压一遍可能是 重复劳动——要按架构决定 压缩发生在哪里。
gzip / deflate / brotli:选型一句话
- gzip:兼容最广,流式支持成熟;
- deflate:细节与 HTTP 里的 raw deflate vs zlib wrapper 容易混,跨语言对接时要 对齐 RFC;
- brotli:同样体积下往往 更小,但 压缩更吃 CPU,适合 预生成静态资源 或 可接受延迟的 API。
Node 里 API 名以 当前文档为准(createGzip、zlib.brotliCompress 等),版本间可能有 异步 Promise 形式 的补充。
流式:createGzip 接到 pipe / pipeline
典型模式:
readable.pipe(zlib.createGzip()).pipe(writable);- 或
stream.promises.pipeline统一处理 错误与关闭。
和 Stream + 背压篇的关系:
- Transform 在中间既读又写,背压 仍然从下游传回上游;
- 不要在
data里手动拼大 Buffer 再write,除非你知道自己在干什么。
一次性 API:小 payload 用
对 已知不大 的 Buffer / 字符串,可以用 gzipSync / gzip(Promise) 等:
- 代码短;
- 但要注意 输入大小上限 与 阻塞事件循环(Sync 版本)。
大对象仍应 流式 或 Worker。
与 HTTP:谁来设 Content-Encoding
若在 Node 里 自己 gzip 响应体:
- 要设
Content-Encoding: gzip; - 注意 ETag / 缓存 与 压缩后内容 一致,否则中间缓存会糊涂。
若前面 反向代理统一压缩:
- 应用往往输出 明文,由代理 协商
Accept-Encoding——少一层逻辑,但要 对齐调试(到底谁压的)。
小结:压缩是「算力换带宽」
- 大流量 优先 流式 + pipeline,守住 内存与背压;
- 算法 在 gzip / brotli 之间按 兼容、CPU、体积 权衡;
- 和网关分工 要想清楚,避免 双重压缩 或 缓存错乱。
把 zlib 和 HTTP、Stream 两篇叠起来看,处理上传下载、静态资源、日志归档会顺很多。