Node.js 压缩与 zlib:gzip、brotli 与流式管线

Stream 篇说过大文件要走流;压缩场景里如果 先读全再压,内存照样炸。
zlib(以及 brotli 等)在 Node 里通常以 Transform 流 或 Promise 版一次性 API 出现。
这一篇想讲清楚几件事:什么时候在 Node 里做压缩、流式管线怎么接、以及和反向代理 gzip 的分工。

常见路径:

  • HTTP 响应里 Content-Encoding: gzip(不少框架或中间件帮你做);
  • 落盘归档、日志打包、大 JSON 上传前压缩;
  • 与对象存储 / CDN 对接时,源站或构建步骤里 先压好再传。

另一层现实:

  • Nginx / CDN 往往已经 对文本类资源做 gzip/br,Node 里再压一遍可能是 重复劳动——要按架构决定 压缩发生在哪里。
  • gzip:兼容最广,流式支持成熟;
  • deflate:细节与 HTTP 里的 raw deflate vs zlib wrapper 容易混,跨语言对接时要 对齐 RFC;
  • brotli:同样体积下往往 更小,但 压缩更吃 CPU,适合 预生成静态资源 或 可接受延迟的 API。

Node 里 API 名以 当前文档为准(createGzip、zlib.brotliCompress 等),版本间可能有 异步 Promise 形式 的补充。

典型模式:

  • readable.pipe(zlib.createGzip()).pipe(writable);
  • 或 stream.promises.pipeline 统一处理 错误与关闭。

和 Stream + 背压篇的关系:

  • Transform 在中间既读又写,背压 仍然从下游传回上游;
  • 不要在 data 里手动拼大 Buffer 再 write,除非你知道自己在干什么。

对 已知不大 的 Buffer / 字符串,可以用 gzipSync / gzip(Promise) 等:

  • 代码短;
  • 但要注意 输入大小上限 与 阻塞事件循环(Sync 版本)。

大对象仍应 流式 或 Worker。

若在 Node 里 自己 gzip 响应体:

  • 要设 Content-Encoding: gzip;
  • 注意 ETag / 缓存 与 压缩后内容 一致,否则中间缓存会糊涂。

若前面 反向代理统一压缩:

  • 应用往往输出 明文,由代理 协商 Accept-Encoding——少一层逻辑,但要 对齐调试(到底谁压的)。
  • 大流量 优先 流式 + pipeline,守住 内存与背压;
  • 算法 在 gzip / brotli 之间按 兼容、CPU、体积 权衡;
  • 和网关分工 要想清楚,避免 双重压缩 或 缓存错乱。

把 zlib 和 HTTP、Stream 两篇叠起来看,处理上传下载、静态资源、日志归档会顺很多。