网站提速:Http2 & server push 实践

作者:JAY 2020-02-29

HTTP/2简介

二进制分帧(Frame)

Http协议分为header和body两部分。在Http1.1中,header必定是ASCII编码的文本,body部分根据Content-Type来决定是文本还是二进制。而在Http2中,header和body都是二进制传输的,并且统称为“帧”(frame)。

Http2一共定义了10种不同的帧,每种有自己的格式,实现特定的功能,以此提高了传输和解析的效率。


头部压缩(Header compression)

Http协议不带状态,每次请求都会带header,而多次请求的header很多时候都是重复的(如又臭又长的CookieToken、User Agent等),带宽就这么白白浪费在复读机上。

Http2要求客户端和服务器维护一个表,header内容都存入表中。这样从第二次请求开始,重复的header只需要发索引,不需要发重复的内容。


数据流&多路复用(Stream & Multiplexing)

建立TCP连接需要3次握手,http1.1使用Keep-Alive,使得多个http请求可以复用同一个TCP连接,串行发送多个http请求。但按顺序发请求有个问题,如果先发过去的请求响应比较慢,后面的请求就得排队等了,这就是“队头堵塞”。

而Http2复用TCP连接,是基于TCP流的。基于上文提到的二进制帧,多个http请求就可以并行发送、并行响应。每个数据包都有自己的唯一标识(Stream Identifiers),客户端发出的一般为奇数,服务器发出的为偶数。客户端通过标识来辨别拿到响应是哪个请求的,这样就不用再按顺序请求和响应,从未避免了队头堵塞。

使用数据流还带来了其他的好处。客户端可以指定数据流的优先级,让服务器优先处理紧急的请求;数据流发送到一半的时候,还可以使用RST_STREAM帧来取消这个数据流,不用像http1.1一样断开tcp连接。


服务端推送(Server push)

Http2允许在响应某个请求的时候,顺便把其他的还没有请求的资源一起推给客户端。这非常适用于Web应用。


Http2实践

以本博客为例,介绍一下Http2实践。首先需要确保网站适用https而不是http(虽然协议没有限制,但浏览器有限制)。然后nginx升级到0.13.9以后的版本。


然后在配置里把“listen 443 ssl;”修改为“listen 443 ssl http2;”。此时,请求已经支持大部分的Http2特性,只有Server Push还需要单独做配置,下文再详细介绍。


打开Chrome的控制台,Network面板,右键勾选Protocol,可以看到请求的协议。之前是“http/1.1”,现在都是“h2”。


Server Push实践

配置Server Push,需要声明推送什么文件,有两种方式。


第一种是直接在nginx里写“http2_push 资源路径”,适用于那些文件名固定、全局需要推送的资源。比如本博客左上角的logo图,任何页面都会加载,可以这样设置:

http2_push /img/logo.png;

打开Chrome的控制台,Network面板,Initiator那一栏,可以看到logo图显示了“Push/Other”。表示这张图片是服务端推送过来的。


第二种是由请求来决定推送的资源。先在nginx里添加这行配置:

http2_push_preload on;

然后在需要推送的场景,通过http header里的link字段,告诉服务器要推送什么资源。


比如打开本博客,可以看到首页的Response header里,带有以下内容:

link: </assets/73b5e095/css/bootstrap.css>; rel=preload; as=style
link: </css/site.css>; rel=preload; as=style
link: </assets/ac7e0294/css/scroll-top.css>; rel=preload; as=style

Nginx判断到上述的link字段,就会自动把资源推送给浏览器。


这种针对性的方式不仅满足了我们精准推送的需求,而且能有效区分服务器配置和应用配置。以上述第三行为例,推送回到顶部插件的CSS文件,为了便于资源缓存和版本更新,使用Hash文件名,每次更新版本路径都会变。使用这种方式,更新版本就只需要更新请求的响应,不用再改nginx配置、重启nginx了。


配置响应头部的方式,不同的语言和框架有自己的实现,此处不深入讨论。本博客使用“yii2-http2-server-push”实现。


项目优化

升级了Http2以后,以往的一些优化经验,可能会不再适用,甚至起到反作用。项目代码需要根据Http2的原理,适当做些调整,以下举3个例子。


以前为了并行下载多个资源,使用多域名储存不同的资源。但现在有了多路复用,直接用同一个域名就行,这样还能减少DNS解析的时间。部署复杂度也降低了许多。


以前网站优化的重要指标,就是减少Http请求,为此做了许多工作,比如把多个js文件,上线前打包成一个;使用CSS Sprites,多个icon合成一张图片等等。用了Http2之后,请求次数不再影响性能,不需要再这么折腾。现在更推荐把文件和资源拆细一些,这样当部分文件更新的时候,其他文件可以继续使用http缓存,性能反而更优。


以前还会使用dataUrl来储存字体、图标,目的也是减少http请求。但现在http请求不会增加额外开销,base64本身却会使二进制文件增加三分之一的大小,还不能触发缓存,反而成了负优化。


总结

本文介绍了HTTP/2协议及Server Push实践。现在大部分的浏览器和客户端都已经支持Http2,强烈推荐使用。

之所以叫Http2而不叫2.0,因为后续不会再有小版本了,下个版本将直接叫Http3(基于QUIC,可以在UDP连接上使用HTTP协议,从而避免TCP协议的瓶颈)。


参考文献

  1. https://tools.ietf.org/html/rfc7540


本文未经许可禁止转载,如需转载请联系 JAY@oonne.com


TOP