相信大家都或多或少听过 HTTP/2 这个名词,听起来好像还很遥远,然而 Web 技术一直在突飞猛进,Google 在 16 年初放弃了对 SPYD 协议的支持,将相关服务切到了 HTTP/2。浏览器方面,Chrome 40+ 和 Firefox 36+ 都正式支持了 HTTP/2;服务器方面, Nginx 已经正式支持 HTTP/2。
HTTP/2 的由来
SPDY 是 Google 开发的一个实验性协议,于 2009 年年中发布,其主要目标是通过解决 HTTP/1.1 中广为人知的一些性能限制来减少网页的加载延迟。具体来说,这个项目设定的目标如下:
- 页面加载时间 (PLT) 减少 50%。
- 无需网站作者修改任何内容。
- 将部署复杂性降至最低,无需变更网络基础设施。
- 与开源社区合作开发这个新协议。
- 收集真实性能数据,验证这个实验性协议是否有效。
几年后的 2012 年,这个新的实验性协议得到了 Chrome、Firefox 和 Opera 的支持,越来越多的大型网站(如 Google、Twitter、Facebook)和小型网站开始在其基础设施内部署 SPDY。事实上,在被行业越来越多的采用之后,SPDY 已经具备了成为一个标准的条件。
观察到这一趋势后,HTTP 工作组 (HTTP-WG) 将这一工作提上议事日程,吸取 SPDY 的经验教训,并在此基础上制定了官方“HTTP/2”标准。在拟定宣言草案、向社会征集 HTTP/2 建议并经过内部讨论之后,HTTP-WG 决定将 SPDY 规范作为新 HTTP/2 协议的基础。
那么为什么不叫 HTTP/1.2 呢?为了实现 HTTP 工作组设定的性能目标,HTTP/2 引入了一个新的二进制分帧层,该层无法与之前的 HTTP/1.x 服务器和客户端向后兼容,因此协议的主版本提升到 HTTP/2。
HTTP/2 相比 HTTP/1.x 的改进
首部压缩
每个 HTTP 传输都承载着说明传输资源及其属性的首部。在 HTTP/1.x 中,这组元数据是纯文本的形式,通常会增加 500-800 字节的开销,使用 Cookie 的话开销则更大。而 HTTP/2 使用了 HPACK 算法压缩了请求和响应头部元数据。
服务端推送
HTTP/2 服务器除了对最初请求的响应外,还可以向客户端推送额外资源而无需额客户端明确请求(Server Push)。如果主页向服务器发起一个请求,服务器可以响应主页内容、logo 以及样式表等,相当于在 HTML 文档内集合了所有资源,并且可以缓存。
多路复用
HTTP/1.x 中如果客户端想要发起多个并行的请求,那么得建立多个 TCP 连接,并且每个连接一次只交付一个响应,而多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。HTTP/2 中客户端和服务器可以将 HTTP 消息分解为互不依赖的帧,然后交错发送,最后再在另一端把它们重新组装起来。
上图所示的是同一个连接内并行的多个数据流,客户端正在向服务器传输一个 DATA 帧(数据流 5),与此同时,服务器正向客户端交错发送数据流 1 和数据流 3 的一系列帧。因此,一个连接上同时有三个并行数据流。
HTTP/2 的连接
HTTP/2 重新定义了格式化和传输数据的方式,这是通过在高层 HTTP API 和低层 TCP 连接之间引入二进制分帧层来实现的。HTTP/1 的请求和响应报文,都是由起始行、首部和实体正文组成,各部分之间以文本换行符分隔。而 HTTP/2 将请求和响应数据分割为更小的帧,并对它们采用二进制编码。下图中的 Binary Framing 即二进制分帧层。
其中有这么几个概念:
- 帧:HTTP/2 数据通信的最小单位。帧用来承载特定类型的数据,如 HTTP 首部、负荷;或者用来实现特定功能,例如打开、关闭流。每个帧都包含帧首部,其中会标识出当前帧所属的流
- 消息:指 HTTP/2 中逻辑上的 HTTP 消息。例如请求和响应等,消息由一个或多个帧组成
- 流:存在于连接中的一个虚拟通道。流可以承载双向消息,每个流都有一个唯一的整数 ID
- 连接:与 HTTP/1 相同,都是指对应的 TCP 连接
在 HTTP/2 中,同域名下所有通信都在单个连接上完成,这个连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装。下图可以说明帧、消息、流和连接的关系。
带来的好处
一个问题
一个页面重要资源的加载速度对于用户体验是至关重要的,如果关键的 JS,顶部 CSS 加载缓慢,则会阻塞页面渲染或使用户无法交互。那么如何让重要的资源更快加载完呢?
HTTP/1 的方案
如果页面采用资源内联的方式,即 CSS、JS 直接内联在 HTML 中,虽然用户在第一次访问时有速度优势,但一般我们不会缓存 HTML 页面,这样的话内联资源没办法利用浏览器缓存,后面的每次访问都是一种浪费。
再来看看资源外链的情况。外链资源一般都会部署在 CDN 上,用户加载外链资源的时间,很大程序取决于请求发出的时间,主要受三个因素的影响:
- 浏览器阻塞:超过浏览器最大连接数限制时,后续请求就会被阻塞。而且现代浏览器在加载同一域名多个 HTTPS 资源时,会有意等第一个 TLS 连接建立完成再请求其他资源
- DNS 查询:浏览器在建立连接前需要通过 DNS 将域名解析为 IP,第一次访问或者缓存失效时,会耗费几十到几百毫秒
- 建立连接:HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文。这个过程通常也要耗费几百毫秒;
静态资源一般会被设置一个时间较长的缓存头,用户不清除浏览器缓存也不刷新时,第二次访问网页时静态资源会从本地缓存获取不产生网络请求。用户普通刷新时浏览器在请求头带上 If-Modified-Since 或 If-None-Match,服务端返回 304 告知浏览器从本地缓存中获取资源。所以资源外链首次慢,后面快。
既然资源内联和外链各有优劣,何不取长补短呢?我们可以对第一次访问的用户将资源内联,并在页面加载完之后异步加载这些资源的外链版本,同时记录一个 Cookie 标记表示用户来过。用户再次访问这个页面时,服务端就可以输出只有外链版本的页面,减小体积。
这样的话基本上能达到更快加载重要资源的效果了,但是这个方案也存在问题,一份资源内联外链加载了两次,会浪费流量,尤其是在流量宝贵的移动端会降低用户的体验。
HTTP/2 的方案
对于 HTTP/2 来说,要解决前面这个问题就非常简单了,开启”Server Push”即可。HTTP/2 的多路复用特性,使得可以在一个连接上同时打开多个流,双向传输数据。这样的话服务端可以在发送页面 HTML 时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。而且服务端主动推送的资源可以被浏览器缓存,也可以供其他页面使用。如果服务端推动的资源已经被缓存过的话,客户端也可以通过发送 RST_STREAM 帧来拒绝接收。
由此看来,HTTP/2 的 Server Push 能够很好地解决”如何让重要资源尽快加载”这个问题,HTTP/1 时代的优化方案在 HTTP/2 普及之后应该会被逐渐取代。
最后看一个实例:HTTP/2: the Future of the Internet 。同时请求 379 张图片,从 Load time 的对比可以看出 HTTP/2 的速度以及性能优势。
参考资料
- HTTP/2 简介
- HTTP/2 新特性浅析
- HTTP/2: A New Excerpt from High Performance Browser Networking
- WEB 性能权威指南
- HTTP/2 与 WEB 性能优化