2、HTTP协议中的压缩:Accept-Encoding与Content-Encoding、gzip与deflate、Brotli算法

聊到接口资源压缩,HTTP 协议这一层是绕不开的。说白了,它就是浏览器和服务器之间「握手」商量压缩方式的那个过程。我刚开始接触这块时,总觉得不就是加个头嘛,后来线上出过一次问题才明白——这里面的门道,比想象中深。

2.1 协商机制:Accept-Encoding 与 Content-Encoding

HTTP 压缩的核心,是一对请求/响应头。客户端说「我能解哪些格式」,服务器说「我用了哪种格式」。就这么简单。

  • Accept-Encoding:客户端发起的请求头,告诉服务器自己支持的压缩算法。比如 Accept-Encoding: gzip, deflate, br。
  • Content-Encoding:服务器返回的响应头,告诉客户端实际用了哪种算法。比如 Content-Encoding: br。

你想想看,如果服务器返回了 Brotli 压缩的内容,但客户端根本不支持,那就会乱码。所以这个协商过程是必须的。我在项目中遇到过一种情况:某个老旧的内网客户端只支持 gzip,但 CDN 默认返回了 Brotli,结果页面白屏了半小时。嗯,从那以后我养成了一个习惯——永远在降级策略里保留 gzip。

核心原则:服务器必须根据 Accept-Encoding 选择客户端支持的算法,不能自作主张。

2.2 gzip 与 deflate:老牌选手的恩怨

gzip 和 deflate,这两个名字你肯定不陌生。但很多人以为 deflate 就是 gzip 的别称,其实不是。

2.2.1 gzip

gzip 是目前最通用的 HTTP 压缩格式。它基于 DEFLATE 算法,但外面包了一层文件头和校验和。几乎所有浏览器和服务器都支持。我个人习惯在生产环境把 gzip 作为保底选项——兼容性最好,没有之一。

2.2.2 deflate

deflate 这个名字其实是个历史误会。HTTP 协议里的 deflate,本意是指 zlib 格式(zlib 头 + DEFLATE 数据流),但有些实现把它理解成了 原始 DEFLATE 数据流。这就导致了兼容性问题。我记得有一次排查接口响应乱码,查了半天发现是 Nginx 和客户端对 deflate 的理解不一致。从那以后,我建议团队直接放弃 deflate,统一用 gzip 或 Brotli。

特性 gzip deflate
底层算法 DEFLATE DEFLATE(zlib 或原始)
文件头 10 字节 gzip 头 2 字节 zlib 头(或没有)
校验和 8 字节 CRC32 4 字节 Adler-32
浏览器兼容性 几乎 100% 存在歧义,部分浏览器不支持
压缩率 中等 略低于 gzip(头开销小)

避坑指南:我曾经在生产环境遇到过 deflate 导致部分 Android 浏览器无法解压的问题。如果你不是必须兼容极端老旧系统,不要用 deflate。gzip 更安全。

2.3 Brotli 算法:新一代压缩之王

Brotli 是 Google 在 2015 年推出的压缩算法。它的目标很明确——比 gzip 压缩率更高,解压速度更快。我最早在 Google 的 PageSpeed 模块里接触到它,当时就觉得这玩意儿有前途。

2.3.1 为什么 Brotli 更强?

  • 更大的字典:Brotli 内置了一个 120KB 的静态字典,包含常见的 HTML、CSS、JS 片段。gzip 没有这个。
  • 更智能的上下文建模:它能根据上下文预测下一个符号,减少冗余。
  • 多级压缩:从 0(最快)到 11(最慢但最小),你可以根据场景选择。

你想想看,同样一份 100KB 的 JavaScript 文件,gzip 能压到 30KB,Brotli 能压到 22KB。对于移动端用户来说,这 8KB 的差距可能就是 200ms 的加载时间。

2.3.2 实际使用建议

我个人习惯在 CDN 和反向代理层同时开启 gzip 和 Brotli。客户端优先选择 Brotli,不支持则降级到 gzip。Nginx 的配置大概是这样:

# 启用 Brotli 压缩
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript image/svg+xml;

# 保留 gzip 作为降级
gzip on;
gzip_comp_level 5;
gzip_types text/html text/css application/javascript;

小技巧:Brotli 的压缩级别 4-6 是性价比最高的区间。级别 11 虽然压缩率最高,但压缩时间可能增加 10 倍,适合静态资源预压缩。动态响应用级别 4 就够了。

2.4 三种算法对比一览

算法 压缩率 压缩速度 解压速度 浏览器支持 推荐场景
gzip 中等 快 快 所有浏览器 保底方案、动态响应
deflate 略高于 gzip 快 快 部分浏览器有歧义 不推荐
Brotli 高(比 gzip 高 15-25%) 慢(级别越高越慢) 快 现代浏览器(Chrome、Firefox、Safari 11+) 静态资源、HTTPS 场景

2.5 知识体系:HTTP 压缩协商流程

下面这张图,是我自己画的一个流程。它把客户端、服务器、CDN 之间的协商过程串起来了。你看一遍,应该就能记住。

HTTP 压缩协商流程图 HTTP 压缩协商流程 客户端 CDN / 反向代理 源服务器 Accept-Encoding: gzip, br 转发请求 Content-Encoding: br Content-Encoding: br CDN 判断客户端支持 返回 Brotli 压缩 降级为 gzip 支持 br 不支持 br 说明 客户端通过 Accept-Encoding 声明能力,服务器或 CDN 根据支持情况选择最优算法。 Brotli 优先,gzip 保底,deflate 建议弃用。

2.6 实战中的几个坑

最后,分享几个我踩过的坑。希望能帮你省点时间。

  • 不要对图片用 gzip/Brotli:JPEG、PNG 已经是压缩格式,再压一遍不仅没效果,还浪费 CPU。我见过有人对图片也开 gzip,结果压缩率不到 1%。
  • 注意 HTTPS 与 Brotli 的关系:Brotli 在 HTTP/2 下表现最好,但 HTTP/1.1 也支持。不过有些老版代理会剥离 Brotli 头,导致降级。我曾经排查过一个 CDN 问题,发现是中间代理把 Accept-Encoding: br 给删了。
  • 预压缩静态资源:对于不常变的 JS/CSS,我建议在构建阶段就生成 .gz 和 .br 文件。Nginx 可以直接用 gzip_static on 和 brotli_static on 来 serve,省去实时压缩的开销。

一句话总结:Brotli 是首选,gzip 是保底,deflate 忘掉它。HTTP 压缩协商就是客户端说「我能吃啥」,服务器说「给你这个」——别搞复杂了。