使用 Brotli 压缩,效果优于 Gzip

HTTP 压缩

Brotli 是一个开源数据压缩库,由 IETF 草案正式规范。它可以用来压缩发送到浏览器的 HTTPS 响应,替代 gzip 或 deflate。

对 Brotli 内容编码的支持已最近加入,现在可以在 Firefox 开发者版(Firefox 44)中进行测试。在这篇文章中,我们将向您展示如何设置一个简单的 HTTPS 服务器,在客户端支持的情况下利用 Brotli 进行压缩。

在通过网络提供内容时,一个简单的优化或低垂的果实是开启服务器端压缩。有点违反直觉的是,在服务器端额外进行 HTTP 响应压缩,并在客户端解压缩,比不进行额外工作要快。这是由于网络带宽的限制。当内容很大、未经压缩(重新压缩不会带来任何好处,除非你是Pied Piper)并且通信成本相对较高时,添加压缩可以缩短传输时间。

用户代理、客户端或 Web 浏览器向服务器发出其可以解压缩哪些压缩内容的信号的方式是通过 `Accept-Encoding` 头。让我们看看 Firefox 43(在支持 Brotli 之前)中的此类头可能是什么样子,开发者工具

Accept-Encoding FF 41以及 Firefox 44(支持 Brotli)中的头

Accept Encoding FF 44

客户端支持这些编码并不意味着它们就会收到这些编码。服务器负责决定选择哪种编码。服务器甚至可能不支持任何形式的压缩。

然后,服务器会使用 `Content-Encoding` 头进行响应,指定使用了哪种压缩形式,如果没有则不指定。

Content Encoding

虽然客户端发送其支持的编码列表,但服务器选择一个进行响应。使用不支持的内容编码进行响应,或使用与内容的实际编码不匹配的头可能会导致解压缩错误,并召唤Z͈͈̩͔̹͙͂͆ͨ̂͒́̌͋ͩ͑̄̔̏́̕ͅĄ̸̢̤͚̜̰̺̉͗̂͋̈͋̏̎̌ͬ͊̾͟L̵͈̠̯͙̠̩͚̰̖̬̩̂̐͐̐̇͑ͥͩ̇͐̄̇̀̕͡G̵̡͋̄͛̈́̓҉̶͉̳̮̣́Ő̐̈̀͐̅ͦ̔͊̽́̅̏͏̦̫̹̖̯͕̙̝̹̳͕͢͜。

Zalgo Decompression Errors

大多数浏览器都支持 gzip 和 deflate(当然还有未压缩的内容)。基于 Gecko 的浏览器(如 Firefox 44 及更高版本)现在支持“br”表示 brotli。Opera Beta 33 支持 lzma(注意:lzma1 而不是 lzma2)和 sdch。这里是 Brotli 支持的相关 Chromium 错误。

创建我们的服务器

这是一个简单的 Node.js 服务器,它使用5 段生成的 Lorem Ipsum 文本进行响应。注意:你需要安装 Node.js,我使用的是 Node v0.12.7。你需要安装 C++ 编译器才能安装我使用的原生插件

npm install accepts iltorb lzma-native

最后,你需要生成一些 TLS 证书来进行测试,因为 Firefox 44 及更高版本支持通过 HTTPS 进行 Brotli 压缩,但不支持 HTTP。如果你在家跟着操作,并且没有看到 Accept-Encoding:“br”,请确保你通过 HTTPS 连接。

你可以按照这里的教程生成自签名证书。请注意,你需要安装 openssl,并且浏览器会发出警告,因为你新生成的证书未被它们或其受信任的证书颁发机构识别。在使用自己生成的并信任的证书进行本地开发时,可以安全地忽略这些警告,但在浏览网络时不要随意忽略证书错误。

这是我们简单服务器的代码。

#!/usr/bin/env node

var accepts = require('accepts');
var fs = require('fs');
var https = require('https');
var brotli = require('iltorb').compressStream;
var lzma = require('lzma-native').createStream.bind(null, 'aloneEncoder');
var gzip = require('zlib').createGzip;

var filename = 'lorem_ipsum.txt';

function onRequest (req, res) {
  res.setHeader('Content-Type', 'text/html');

  var encodings = new Set(accepts(req).encodings());

  if (encodings.has('br')) {
    res.setHeader('Content-Encoding', 'br');
    fs.createReadStream(filename).pipe(brotli()).pipe(res);
  } else if (encodings.has('lzma')) {
    res.setHeader('Content-Encoding', 'lzma');
    fs.createReadStream(filename).pipe(lzma()).pipe(res);
  } else if (encodings.has('gzip')) {
    res.setHeader('Content-Encoding', 'gzip');
    fs.createReadStream(filename).pipe(gzip()).pipe(res);
  } else {
    fs.createReadStream(filename).pipe(res);
  }
};

var certs = {
  key: fs.readFileSync('./https-key.pem'),
  cert: fs.readFileSync('./https-cert.pem'),
};

https.createServer(certs, onRequest).listen(3000);

然后,我们可以在浏览器中导航到 https://localhost:3000。让我们看看我在不同浏览器中访问服务器时会发生什么。

Firefox 45 使用 Brotli

Firefox 45 BrotliOpera Beta 33 使用 lzma

Opera 33 lzmaSafari 9 和 Firefox 41 使用 gzip

Safari 9 gzip

我们可以使用 Firefox 开发者工具(在网络选项卡下)比较压缩前后资源的大小,方法是比较“已传输”和“大小”列。已传输列显示通过网络传输的压缩内容的字节数,大小列显示资源的解压缩大小。对于未经任何形式压缩发送的内容,这两者应该相同。

Transferred vs Size

我们还可以使用 curl 命令行实用程序进行验证


$ curl https://localhost:3000 --insecure -H 'Accept-Encoding: br' -w '%{size_download}' -so /dev/null
1333

$ curl https://localhost:3000 --insecure -H 'Accept-Encoding: lzma' -w '%{size_download}' -so /dev/null
1502

$ curl https://localhost:3000 --insecure -H 'Accept-Encoding: gzip' -w '%{size_download}' -so /dev/null
1408

$ curl https://localhost:3000 --insecure -w '%{size_download}' -so /dev/null
3484

关于压缩与性能的说明

使用哪种压缩方案会产生影响。Node.js 自带 zlib,但是包含 lzma 和 brotli 的原生 Node 插件会稍微增加分发大小。各种压缩引擎运行所需的时间可能差异很大,并且在压缩内容时使用的内存可能会在处理大量请求时达到物理限制。

在前面的示例中,您可能已经注意到,lzma 在开箱即用时并没有超过 gzip 的压缩率,brotli 也只略微超过。您应该注意,所有压缩引擎都有许多配置选项,可以对其进行调整以权衡性能、内存使用等方面。我们将接下来看看响应时间、内存使用和 Weissman 得分的变化情况。

以下数字是从运行中收集的


$ /usr/bin/time -l node server.js &
$ wrk -c 100 -t 6 -d 30s -H 'Accept-Encoding: <either br lzma gzip or none>' https://localhost:3000
$ fg
ctrl-c

以下测量是在以下机器上进行的:2013 年初 Apple MacBook Pro OSX 10.10.5 16GB 1600 MHz DDR3 2.7 GHz Core i7 4 核,支持超线程。

压缩方法 每秒请求数 已传输字节数 (MB/s) 最大 RSS (MB) 平均延迟 (ms)
br-stream 203 0.25 3485.54 462.57
lzma 233 0.37 330.29 407.71
gzip 2276 3.44 204.29 41.86
4061 14.06 125.1 23.45
br-static 4087 5.85 105.58 23.3

一些需要注意的数字

  • 除了 gzip 之外的压缩方法,每秒请求数都会出现性能下降。
  • 压缩流的内存使用量明显更高。brotli 的峰值 RSS 为 9.8 GB 3.4 GB,看起来像是内存泄漏,已在 upstream 报告(当我看到这个数字时,我的单片眼镜都掉了)。
  • 测量的延迟仅来自本地主机,在互联网上的延迟至少会这么高,甚至可能更高。这是开发者工具 > 网络 > 定时中显示的等待时间
  • 如果我们使用从源代码构建的 brotli 预先压缩静态资源,则可以获得极佳的结果。注意:我们只能对静态响应使用此技巧。
  • 提供静态 Brotli 压缩响应的性能与提供静态未压缩资源的性能一样好,同时使用的内存略少。这是有道理的,因为传输的字节数更少!每秒传输的字节数越少,使得该变量似乎与要传输的文件中的字节数无关。

为了预先压缩静态资源,我们可以从源代码构建 brotli,然后运行


$ ./bro --input lorem_ipsum.txt --output lorem_ipsum.txt.br

并修改我们的服务器


4d3
< var brotli = require('iltorb').compressStream;
8c7
< var filename = 'lorem_ipsum.txt'; --- > var filename = 'lorem_ipsum.txt.br';
17c16
< fs.createReadStream(filename).pipe(brotli()).pipe(res); --- >     fs.createReadStream(filename).pipe(res);

BREACH 攻击

与其他 HTTP 压缩机制一样,在 HTTPS 中使用 Brotli 可能会使您容易受到BREACH 攻击。如果你想使用它,你应该应用其他 BREACH 缓解措施

结论

对于 5 段 Lorem Ipsum 文本,Brotli 比 gzip 的压缩率高 5%。如果我对 2015 年 10 月 1 日的 reddit.com 首页运行相同的实验,Brotli 比 gzip 的压缩率高 22%!请注意,这两个测量值都使用开箱即用的压缩器,没有任何配置值的调整。

你的用户群中是否有很大一部分用户使用支持 Brotli 作为内容编码的浏览器、增加的延迟和内存成本是否值得、你的 HTTPS 服务器或 CDN 是否支持 Brotli 则是另一个问题。但如果你正在寻找比 gzip 更佳的性能,Brotli 看起来是一个可能的竞争者。

关于 Nick Desaulniers

Nick Desaulniers 的更多文章…


6 条评论

  1. Dan Callahan

    出于好奇,为什么 Brotli 压缩被限制在安全连接上,以及为什么没有像 ServiceWorker 一样对本地开发(Bug 1222541)进行豁免?

    2015 年 11 月 6 日 上午 10:48

  2. Nick Desaulniers

    嗨,Dan,这是一个很好的问题。随着越来越多的 API 依赖于 HTTPS,我们需要对安全上下文有一个更明确的定义。事实上,目前有一个规范涵盖了所有边缘情况:https://w3c.github.io/webappsec-secure-contexts/

    这将有助于我们减少 Gecko 中的代码重复。理论上,一旦此补丁生效https://bugzilla.mozilla.org/show_bug.cgi?id=1162772,并且我们去除了 Gecko 中检查安全上下文的重复部分,这个问题将自行解决。

    2015 年 11 月 6 日 上午 10:54

  3. Mindaugas

    看看结果,坦率地说,在我看来,这似乎是一项毫无意义的努力。当我们甚至无法在任何地方获得合适的 lzma 支持时,为什么要使用另一种新的压缩算法,即使它是 FOSS 也一样 AFAIK?

    维基百科声称 brotli 应该与 deflate 一样快?!

    我有一种感觉,对动态内容使用 gzip/deflate,对静态内容使用 lzma 是理想的,但这可能只是因为我生活在一个浏览器供应商想要合作的梦幻世界里

    2015 年 11 月 6 日 下午 1:02

    1. Nick Desaulniers

      > 为什么在无法在任何地方获得合适的 lzma 支持时还要使用另一种新的压缩算法,即使它是 FOSS 也一样 AFAIK?

      我的意思是,很多 Web API 在每个浏览器中都不可用。我们不应该失去希望,相反,我们应该请求我们的浏览器供应商支持我们最喜欢的功能。当他们有公开的错误跟踪器时,这会有帮助。

      > 维基百科声称 brotli 应该与 deflate 一样快?!

      对于各种配置组合,这可能是正确的。

      > 我生活在一个浏览器供应商想要合作的梦幻世界里

      浏览器市场是一个有趣的健康竞争与合作的格局。

      2015 年 11 月 6 日 下午 2:48

  4. Eric Lawrence

    将 brotli 限制在安全上下文中是因为中间体(特别是错误的代理和内容扫描程序)在遇到非 deflate/gzip Content-Encoding 时往往表现得很糟糕。Google 的人之前在推出“sdch”和“bzip2”时发现了这一点;他们最终部分出于这个原因而取消了 bzip2,并且 sdch 有许多他们不得不加入的技巧。通过要求 Brotli 使用 HTTPS,他们可以在大多数情况下解决这个问题,因为相对较少的内容扫描程序会拦截 HTTPS 流。

    我写了一些关于 Brotli 支持的文章:http://textslashplain.com/2015/09/10/brotli/http://textslashplain.com/2015/10/10/fiddler-and-brotli/

    2015 年 11 月 6 日 下午 3:03

    1. Nick Desaulniers

      感谢 Eric 的澄清!顺便说一句,非常棒的文章。令人着迷的发现,是对这篇文章的完美补充。

      2015 年 11 月 6 日 下午 3:23

本文评论已关闭。