引入 Brotli 进行 Web Server的压缩

目前我们大多数网站都是启用的 gzip 来进行 资源传输的压缩,这有利于我们更加快速的访问我们的网站;但是除了 gzip 外,我们或许还有别的选项,而且性能会更好,比如 Facebook 便是启用的 Brotli 算法;

Brotli

Brotli 最初发布于2015年,用于网络字体的离线压缩。Google软件工程师在2015年9月发布了包含通用无损数据压缩的Brotli增强版本,特别侧重于HTTP压缩。新版本还展现了跨平台的性能改进,以及减少解码所需的内存。

与常见的通用压缩算法不同,Brotli使用一个预定义的120千字节字典。该字典包含超过13000个常用单词、短语和其他子字符串,这些来自一个文本和HTML文档的大型语料库。预定义的算法可以提升较小文件的压缩密度。

对全球1000个访问量比较大的地址进行测试,使用 Brotli 算法可以得到明显的性能改善:

  • 14% smaller than gzip for JavaScript
  • 21% smaller than gzip for HTML
  • 17% smaller than gzip for CSS

使用brotli取代deflate来对文本文件压缩通常可以增加20%的压缩密度,而压缩与解压缩速度则大致不变。

当然如同 HTTP2, 我们还需要考虑浏览器的兼容性:

因此我们在设计自己程序还需要考虑到对于不支持 Brotli 压缩的进行 gzip 的降级处理;

Nginx 启用 Brotli

你可以参考 这篇文章 实现 ngx_brotli 的下载安装;

ngx_brotli 配置有如下指令:

  • brotli 是否启用 Brotli on 为开启 off 为关闭
  • brotli_types 允许的压缩 mime 类型比如 text/css, text/javascript
  • brotli_comp_level 压缩级别 可选值范围为0~11,默认值为6

配置参考

http {
    ...
    gzip  on;
    gzip_vary               on;
    gzip_min_length         1024;
    gzip_buffers            128 32k;
    gzip_comp_level         6;
    gzip_http_version       1.1;
    gzip_proxied            expired no-cache no-store private auth;
    gzip_types              text/plain text/css text/xml application/xml application/json text/javascript application/javascript application/x-javascript;

    brotli on;
    brotli_types text/plain text/css text/xml application/xml application/json text/javascript application/javascript application/x-javascript;
    brotli_static off;
    brotli_comp_level 11;
    brotli_buffers 16 8k;
    brotli_window 512k;
    brotli_min_length 20;
    ...
}

改完之后可以记得 重启 nginx 查看 header 头是不是有这样的值:

Content-Encoding: br

Node.js 中使用

npm i iltorb zlib accepts --save

我们以 egg.js 中间件为案例。

const isJSON = require('koa-is-json')
const accepts = require('accepts')
const zlib = require('zlib')
const brotli = require('iltorb')

module.exports = options => {
  return async function gzip (ctx, next) {
    await next()
    let body = ctx.body
    if (!body) return
    const encodings = new Set(accepts(ctx.req).encodings())
    // 支持 options.threshold
    if (options.threshold && ctx.length < options.threshold) return

    if (isJSON(body)) {
      body = JSON.stringify(body)
    }
    // 判断 accept 是否支持 brotli 压缩
    if (encodings.has('br')) {
      const stream = brotli.compressStream()
      stream.flush = function () {}
      stream.end(body)
      ctx.body = stream
      ctx.set('Content-Encoding', 'br')
    } else {
       // 设置 gzip body,修正响应头
      const stream = zlib.createGzip()
      stream.end(body)
      ctx.body = stream
      ctx.set('Content-Encoding', 'gzip')
    }
  }
}

记得在配置中启用这个中间件。

还有一点需要提醒大家,brotli 压缩只能在 https 中生效,因为 在 http 请求中 request header 里的
Accept-Encoding: gzip, deflate 是没有 br 的。

参考代码

扩展阅读