Nginx 为站点启用 Brotli 压缩算法_服务器

无论你在做前端、后端还是运维,HTTP都是不得不打交道的网络协议。它是最常用的应用层协议,对它的优化,既能通过降低时延带来更好的体验性,也能通过降低资源消耗带来更高的并发性。

可是,学习HTTP不久的同学,很难全面说出HTTP的所有优化点。这既有可能是你没好好准备过大厂的面试,也有可能你没有加入一个快速发展的项目,当产品的用户量不断翻番时,需求会倒逼着你优化HTTP协议。

这篇文章是根据我在2019年GOPS全球运维大会上海站的演讲PPT,重新提炼文字后的总结。我希望能从四个全新的维度,带你覆盖绝大部分的HTTP优化技巧。这样,即使还不需要极致方法去解决当前的性能瓶颈,也会知道优化方向在哪,当需求来临时,能够到Google上定向查阅资料。

第一个维度,是从编码效率上,更快速地把消息转换成更短的字符流。这是最直接的性能优化点。

 

编码效率优化


如果你对HTTP/1.1协议做过抓包分析,就会发现它是用“whitespace-delimited”方式编码的。用空格、回车来编码,是因为HTTP在诞生之初追求可读性,这样更有利于它的推广。

然而在当下,这种低效的编码方式已经严重影响性能了,所以2009年Google推出了基于二进制的SPDY协议,大幅提升了编码效率。2015年,稍做改进后它被确定为HTTP/2协议,现在50%以上的站点都在使用它。

Nginx 为站点启用 Brotli 压缩算法_字段_02

 这是编码优化的大方向,包括即将推出的HTTP/3。

然而这些新技术到底是怎样提升性能的呢?那还得拆开了看,先从数据的压缩谈起。你抓包看到的是数据,它并不等于信息。数据其实是信息和冗余数据之和,而压缩技术,就是尽量地去除冗余数据。

Nginx 为站点启用 Brotli 压缩算法_nginx_03

 压缩分为无损压缩和有损压缩。针对图片、音视频,我们每天都在与有损压缩打交道。比如,当浏览器只需要缩略图时,就没有必要浪费带宽传输高清图片。而高清视频做过有损压缩后,在肉眼无法分清时,已经被压缩了上千倍。这是因为,声音、视频都可以做增量压缩。

还记得曾经的VCD吗?当光盘有划痕时,整张盘都无法播放,就是因为那时的视频做了增量压缩,而且关键帧太少,导致关键帧损坏时,后面的增量帧全部无法播放了。

再来看无损压缩,你肯定用过gzip,它让http body实现了无损压缩。肉眼阅读压缩后的报文全是乱码,但接收端解压后,可以看到发送端的原文。然而,gzip的效率其实并不高,以Google推出的brotli做对比,你就知道它的缺陷了:

Nginx 为站点启用 Brotli 压缩算法_服务器_04

 评价压缩算法时,我们重点看两个指标:压缩率和压缩速度。上图中可以看到,无论用gzip 9个压缩级别中的哪一个,它的压缩率都低于brotli(相比gzip,压缩级别它还可以配置为10),压缩速度也更慢。所以,如果可以,应该尽快更新你的gzip压缩算法了。

 

内容协商


在介绍 Brotli 之前,先说一下 HTTP 内容协商

同一个 URL有时可以提供不同格式的页面,有存文本的、有压缩的,压缩也有不同算法的,这就要求服务端和客户端之间有一个选择最合适版本的机制,这就是内容协商。

HTTP 的内容协商的其中一种方式:服务端根据客户端发送的请求头中某些字段自动发送最合适的版本。

Nginx 为站点启用 Brotli 压缩算法_字段_05

 

可以用于这个机制的请求头字段又分两种:内容协商专用字段(Accept 字段)、其他字段。

字段情况,详见下表:

请求头字段

说明

响应头字段

Accept

告知服务器可以发送何种媒体类型

Content-Type

Accept-Language

告知服务器可以发送何种语言

Content-Language

Accept-Charset

告知服务器可以发送何种字符集

Content-Type

Accept-Encoding

告知服务器可以采用何种压缩方式

Content-Encoding

例如客户端发送以下请求头:

1

Accept-Encoding:gzip,deflate,br

表示支持采用 gzip、deflate 或 br 压缩过的资源

浏览器的响应头可能是这样的:

1

Content-Encoding: gzip

如果你的服务器支持br压缩会反回:

Nginx 为站点启用 Brotli 压缩算法_字段_06

 

 

什么是 Brotli


 在web应用中,为了节省流量,降低传输数据大小,提高传输效率,常用的压缩方式一般都是​​gzip​​,今天我们来介绍另外一种更高效的压缩方式brotli。

Brotli 是 google 开发的文本压缩算法,比起gzip可能会有高达37%的提升。主流浏览器都支持br算法,强烈大家升级改算法。使用算法的前提是启用了 https(它仅适用于 HTTPS),因为 http 请求中 request header 里的 Accept-Encoding: gzip, deflate 是没有 br 的。

Brotli is a generic-purpose lossless compression algorithm that compresses data using a combination of a modern variant of the LZ77 algorithm, Huffman coding and 2nd order context modeling, with a compression ratio comparable to the best currently available general-purpose compression methods. It is similar in speed with deflate but offers more dense compression.

Brotli 是基于LZ77算法的一个现代变体、霍夫曼编码和二阶上下文建模。Google软件工程师在2015年9月发布了包含通用无损数据压缩的Brotli增强版本,特别侧重于HTTP压缩。其中的编码器被部分改写以提高压缩比,编码器和解码器都提高了速度,流式API已被改进,增加更多压缩质量级别。

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

使用Brotli替换Deflate来对文本文件压缩通常可以增加20%的压缩密度,而压缩与解压缩速度则大致不变。Brotli 压缩只能在https中生效,因为 在 http 请求中 request header 里的 Accept-Encoding: gzip, deflate 是没有 br 的。

 

 

浏览器对brotli协议的支持


  • Mozilla Firefox在Firefox 44中实现Brotli。
  • Google Chrome从Chrome 49开始支持Brotli。
  • Opera从Opera 36开始支持Brotli。

Nginx 为站点启用 Brotli 压缩算法_字段_07

 各种压缩算法的在不同level下的比较

Nginx 为站点启用 Brotli 压缩算法_服务器_04

 

Brotli压缩算法应用在HTTP请求流程


  1. ​​用户访问支持Brotli压缩的HTTP服务器上的网站或者Web应用
  2. 浏览器通过使用Accept-Encoding标头通知HTTP服务器它支持解压缩的内容类型
  3. HTTP服务器根据请求中包含的压缩算法决定要哪种压缩内容
  4. 服务器通过返回头部Content-Encoding向浏览器端表明数据的压缩方式
  5. 浏览器将数据解压并展示在页面上

 

安装与配置


本教程是在原来已安装nginx,但没有编译br模块的前提下,增加br模块,开始前需要先关闭nginx服务。

安装ngx_brotli

[root@localhost deps]# cd /usr/local/src/
[root@localhost src]# git clone https://github.com/google/ngx_brotli
.........................
Receiving objects: 100% (203/203), 82.55 KiB | 66.00 KiB/s, done.
Resolving deltas: 100% (87/87), done.
[root@localhost src]# cd ngx_brotli && git submodule update --init
..........................
Resolving deltas: 100% (4006/4006), done.
Submodule path 'deps/brotli': checked out 'e61745a6b7add50d380cfd7d3883dd6c62fc2c71'

 获取编译参数Nginx Arguments

[root@localhost ~]# /usr/local/nginx/sbin/nginx  -V
nginx version: nginx/1.16.1
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)
configure arguments: --prefix=/usr/local/nginx

整理新的Arguments

根据获取到的configure arguments和上面软件的位置,重新整理configure arguments

然后再加上 ​​--add-module=/usr/local/src/ngx_brotli​

[root@localhost nginx-1.16.1]# ./configure --prefix=/usr/local/nginx --add-module=/usr/local/src/ngx_brotli
[root@localhost nginx-1.16.1]# make && make install
[root@localhost nginx-1.16.1]# make upgrade
/usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
kill -USR2 `cat /usr/local/nginx/logs/nginx.pid`
sleep 1
test -f /usr/local/nginx/logs/nginx.pid.oldbin
kill -QUIT `cat /usr/local/nginx/logs/nginx.pid.oldbin`

[root@localhost nginx-1.16.1]# /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.16.1
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)
configure arguments: --prefix=/usr/local/nginx --add-module=/usr/local/src/ngx_brotli

找到Nginx的全局配置文件

例如:Brotli和gzip是可以并存的,无需关闭gzip。 

http {
...
# gzip
gzip on;
gzip_min_length 1k;
gzip_buffers 4 32k;
gzip_http_version 1.1;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
gzip_vary on;
gzip_proxied any;
gzip_disable "MSIE [1-6]\.";

# brotli
brotli on;
brotli_comp_level 6;
brotli_buffers 16 8k;
brotli_min_length 20;
brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;
...
}

检测配置是否正常

/use/local/nginx/sbin/nginx -t
/use/local/nginx/sbin/nginx -s reload

 

检查br是否生效


curl检测gzip编码

[root@localhost ~]# curl -IL https://www.nginx.cn -H "Accept-Encoding: gzip"
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 16 Oct 2020 07:09:11 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/7.3.11
Link: <https://www.nginx.cn/wp-json/>; rel="https://api.w.org/"
Last-Modified: Fri, 16 Oct 2020 06:55:41 GMT
ETag: "ac905e1aa0e64f78308eb96c168163cb"
Content-Encoding: gzip
Vary: Accept-Encoding
Link: <https://www.nginx.cn/wp-json/>; rel="https://api.w.org/"
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
Referrer-Policy: no-referrer-when-downgrade

curl检测br编码

或者使用浏览器工具:

检查 > Network > Headers > "Response Headers" > "Content-Encoding" header,发现有​​content-encoding:br​

Nginx 为站点启用 Brotli 压缩算法_nginx_09

 高级应用:

gzip和br可以同时生效,意味着我们可以对基于 Node.js 等会在后台自动 Gzip 的站点,设置优先使用 brotli,所以在网站反代配置里加上如下配置,告知后端:前端不接受 Gzip 编码。

在反向代理配置文件代码中添加:

proxy_set_header Accept-Encoding "";

例如:

server {    ...    
location / {
...
proxy_set_header Accept-Encoding "";
...
}
}