浏览器和 HTTP 客户端用 gzip、brotli、zstd 压缩响应。brotli 为什么能在小页面上更小?requests 默认会发什么?对已经压缩的内容再压会怎样?现压缩和预压缩怎么选?这篇在本机服务器上逐项测。
输入 原字节 gzip-9 zstd-19 brotli q11 brotli q5 英文 HTML 样板(导航、页脚) 573 333 348 203 243 英文 HTML 样板前 300 字节 300 214 215 122 153 合成乱码词(我的 VOC 词表) 573 344 351 268 344 products JSON 3 条 1054 506 500 437 481 一页 13 KiB 合成页面 13723 3863 3791 3048 3764
brotli 自带一份静态字典,里面是英文词和 HTML、JavaScript、CSS 的常见片段。实验里 q5 对合成乱码词和 gzip 一样大(344 字节),对英文 HTML 样板却小 27%;这个差别应该来自字典,但我没有读字典文件,只是推断。
输入 原字节 gzip-6 gzip-9 zstd-3 zstd-19 br q5 br q9 br q11 一页 HTML 13 KiB 13723 3878 3863 3898 3791 3764 3774 3048 200 条 products JSON Lines 72453 16349 16125 16318 14773 15444 15224 12742 pages 8 MiB 8388608 1836858 1822313 1670198 1401606 1588548 1536350 1390770
一页 13 KiB 的 HTML,brotli q11 比 gzip-9 小 21%;200 条 JSON 小 21%。几十 KB 的小响应是 HTTP 的典型大小,所以收益明显。
requests 2.34.2 默认发出的 Accept-Encoding:'gzip, deflate, br, zstd';服务器选了 br,线上正文 129879 字节,解码后 723399 字节 Accept-Encoding: identity 请求 /data → Content-Encoding=None,线上 723399 字节,解码后 723399 字节,解码正确 Accept-Encoding: identity 请求 /page → Content-Encoding=None,线上 13723 字节,解码后 13723 字节,解码正确 Accept-Encoding: gzip 请求 /data → Content-Encoding=gzip,线上 150532 字节,解码后 723399 字节,解码正确 Accept-Encoding: gzip 请求 /page → Content-Encoding=gzip,线上 3890 字节,解码后 13723 字节,解码正确 Accept-Encoding: br 请求 /data → Content-Encoding=br,线上 129879 字节,解码后 723399 字节,解码正确 Accept-Encoding: br 请求 /page → Content-Encoding=br,线上 3764 字节,解码后 13723 字节,解码正确 Accept-Encoding: zstd 请求 /data → Content-Encoding=zstd,线上 138216 字节,解码后 723399 字节,解码正确 Accept-Encoding: zstd 请求 /page → Content-Encoding=zstd,线上 3898 字节,解码后 13723 字节,解码正确 Accept-Encoding: gzip, br, zstd 请求 /data → Content-Encoding=br,线上 129879 字节,解码后 723399 字节,解码正确 Accept-Encoding: gzip, br, zstd 请求 /page → Content-Encoding=br,线上 3764 字节,解码后 13723 字节,解码正确
客户端用 Accept-Encoding 声明支持的编码,服务器选一个。requests 2.34.2 在装了 brotli 和 zstandard 之后默认发 gzip, deflate, br, zstd;2000 条 JSON 线上 gzip 150532 字节、zstd 138216、br 129879。这也是 Vary: Accept-Encoding 的由来。
内容 原字节 gzip-6 br q5 zstd-3 随机字节(像 JPEG/加密数据) 1048576 1048902 1048581 1048609 已经 gzip 过的文本 230952 231033 230957 230967 原始文本(未压缩) 1048576 230940 200268 209800
随机字节(像 JPEG、加密数据)和已经压缩过的文本再压一次不会变小,还略大。HTTP 压缩应当只用于文本类内容。
gzip level 1 4072 字节 中位 44 微秒/页 → 单核每秒约 22727 页 gzip level 6 3878 字节 中位 94 微秒/页 → 单核每秒约 10667 页 gzip level 9 3863 字节 中位 134 微秒/页 → 单核每秒约 7474 页 zstd level 3 3898 字节 中位 26 微秒/页 → 单核每秒约 37915 页 zstd level 19 3791 字节 中位 2292 微秒/页 → 单核每秒约 436 页 brotli q4 3874 字节 中位 93 微秒/页 → 单核每秒约 10801 页 brotli q5 3764 字节 中位 150 微秒/页 → 单核每秒约 6670 页 brotli q9 3774 字节 中位 218 微秒/页 → 单核每秒约 4585 页 brotli q11 3048 字节 中位 10993 微秒/页 → 单核每秒约 91 页
q4–q5 和 gzip 一个量级,zstd-3 更快(26 微秒/页),q11 每页 11 ms。动态内容用 gzip-6、zstd-3 或 brotli q4–q5;静态文件预先用 q11 压好。
编码 字节 压缩 解压 @1 Mbit/s @10 Mbit/s @100 Mbit/s @1000 Mbit/s identity 13723 0微秒 0微秒 109.78 ms 10.98 ms 1.10 ms 0.11 ms gzip-6 3878 107微秒 11微秒 31.14 ms 3.22 ms 0.43 ms 0.15 ms zstd-3 3898 27微秒 10微秒 31.22 ms 3.15 ms 0.35 ms 0.07 ms br q5 3764 147微秒 20微秒 30.28 ms 3.18 ms 0.47 ms 0.20 ms br q11 3048 10969微秒 31微秒 35.38 ms 13.44 ms 11.24 ms 11.02 ms (总耗时 = 压缩时间 + 字节数×8/带宽 + 解压时间,忽略了延迟、TCP 慢启动、并发;如果压缩是预先做好的静态文件,压缩时间不计入请求)
按“压缩 + 传输 + 解压”的估算:1 Mbit/s 下一页 109.78 ms 降到约 31 ms;1 Gbit/s 下 zstd-3 只有 0.07 ms,而 q11 现压缩是 11.02 ms,不划算。网络越快,压缩的收益越小,压缩的 CPU 开销越要留意。