CDN 压缩协商实测:为什么浏览器总是拿到 gzip
【摘要】 用 Accept-Encoding 矩阵实测两个 CDN 节点的压缩协商:一个只要声明 gzip 就回 gzip,br/zstd 变体形同虚设;另一个正确选中 br 且只用 q4 档。附解压吞吐实测、反推压缩档位的方法与 Vary 缓存坑。
结论先行
对两个不同 CDN 节点的压缩协商做了一轮矩阵实测,结果和直觉相反:
- 其中一个节点的协商是「gzip 优先」:客户端
Accept-Encoding里只要出现gzip,它就返回 gzip——哪怕同时声明了 br 和 zstd。而浏览器真实发送的头正是gzip, deflate, br, zstd,所以永远只能拿到 gzip。只有客户端明确不支持 gzip 时,它才启用 br/zstd。 - 另一个节点的协商是对的:多编码声明下正确选中 br,且实际只用了 q4 质量档,实时压缩开销约 4.6 ms。但它不认 zstd,单独声明
zstd会返回未压缩的 identity。 - brotli 解压并不比 gzip 慢:同一文件 br 解压 462 MB/s、gzip 391 MB/s,br 反而快约 15%。「br 慢」的印象来自 q11 档,而线上用的是 q4。
实测方法
- 测量必须绕过本机代理,否则会污染国内 CDN 的结果,所有请求统一带
--noproxy '*'。 - 手动指定
Accept-Encoding时 curl 不会自动解压,size_download就是压缩后的字节数;用--compressed反而测不到,因为它会自行改写请求头并自动解压。 - 解压吞吐用 Python 的 brotli/gzip/zstandard 模块各跑 200 轮取中位数。
- 反推质量档的办法:先把响应体解压出原始数据,再在本地按 q1/q4/q5/q9/q11 重压,比对字节数落在哪一档。
一、编码协商矩阵
站点 A(原始 JS 1,784,216 B)
| 客户端 Accept-Encoding | 实际返回 | 传输字节 |
|---|---|---|
gzip, deflate, br, zstd(浏览器真实头) | gzip | 517,208 |
gzip, deflate, br | gzip | 517,208 |
gzip, zstd | gzip | 517,208 |
br, gzip | gzip | 517,208 |
zstd, gzip | gzip | 517,208 |
zstd, br | zstd | 422,435 |
br, zstd | zstd | 422,247 |
br | br | 420,954 |
zstd | zstd | 416,099 |
规律很清楚:请求头含 gzip 就回 gzip;不含 gzip 时才在 br/zstd 之间挑选,且 zstd 优先于 br,与声明顺序无关。
站点 B(原始 JSON 438,775 B)
| 客户端 Accept-Encoding | 实际返回 | 传输字节 |
|---|---|---|
gzip, deflate, br, zstd(浏览器真实头) | br | 90,306 |
zstd, br | br | 90,306 |
br, zstd | br | 90,306 |
br | br | 90,306 |
zstd | identity(未压缩) | 438,775 |
gzip | gzip | 96,653 |
这个节点的逻辑是「支持 br 就选 br」,完全不识别 zstd。
二、代价:正确的那一方把客户端锁在了最差的一档
同一个 1.78 MB 的 JS,三种编码分别落盘实测:
| 编码 | 传输大小 | 相对 gzip | 解压耗时 | 解压吞吐 |
|---|---|---|---|---|
| gzip | 517,208 B | — | 4,953 μs | 360 MB/s |
| br | 420,954 B | −18.6% | 3,843 μs | 464 MB/s |
| zstd | 416,099 B | −19.5% | 1,680 μs | 1,062 MB/s |
站点 A 明明有 zstd 能力——单独声明时能拿到,缓存里也有该变体——却因为协商顺序把它锁在门外。对访客来说,这是每次都多传 20% 字节、多花 3 倍解压时间。
三、br 并不慢:反推线上用的质量档
把站点 B 的原始 JSON 在本地按各质量重压,和线上实际传输的 90,306 B 对比:
| 质量档 | 产出 | 压缩耗时 |
|---|---|---|
| q1 | 101,650 B | 2.7 ms |
| q4 | 90,381 B | 4.6 ms |
| q5 | 81,088 B | 5.9 ms |
| q9 | 76,951 B | 16.3 ms |
| q11 | 70,427 B | 377 ms |
线上传的 90,306 B 正好落在 q4 的 90,381 B 附近,说明它用的是 q4,而不是很多人印象里「慢得要死」的 q11。4.6 ms 的实时压缩成本基本可以忽略。
四、几个容易踩的坑
- 变体缓存要预热。CDN 按
Vary: Accept-Encoding把各编码变体分开缓存,首次请求某个变体是 MISS,要回源现压,首测耗时能到 4.8 秒。预热之后三个变体都稳定 HIT。别用「第一次访问」的印象评判某个编码。 - 有的节点不返回 Vary,却按编码返回不同内容。它自己维护了变体缓存,但缺少 Vary 头理论上存在缓存投毒隐患,本轮没有观测到实际问题。
- 反推质量档要控制 lgwin。窗口大小不同,字节数不能直接对标。
- zstd 帧头可能不写 content size。部分实现解压时会报「无法从帧头确定大小」,需要改用
max_output_size或流式解压。
五、建议
对协商有问题的节点,建议先去控制台确认「智能压缩 / Brotli」里有没有算法优先级或 gzip 开关;如果控制台没有这个选项,值得提单——「只要声明了 gzip 就不选 br/zstd」并不符合按 q 值择优的协商精神。对已经正确选 br 且使用 q4 的节点,无需改动,这已经是这条链路的最优解。
本文基于本人真实环境的抓包与压测数据整理,AI辅助撰写与排版;文中站点做了匿名处理,结论均可复现。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)