http2详解 [上]


1. 背景

这篇文档会从技术和协议层面来介绍http2。

如果你有在这篇文章中发现任何我的失误造成的错误或疏漏,请帮我指正。我会在后续版本中修改。

为了让阅读体验更流畅,在这篇文章中我会使用“http2”来指代这一新协议,但请记住该协议的正式名字是HTTP/2。


2. HTTP的现状

几乎所有互联网上的内容都采用了HTTP 1.1作为通信协议。人们在该协议上投入了大量精力,所以基于它的基础架构也得以日臻完善。而得益于此,在现有的HTTP协议之上构建新的方案会比从底层建立新的协议要容易得多。

2.1 HTTP 1.1过于庞大

HTTP刚诞生的时候只被当作是一个相对简单直观的协议,但时间证明了早期的设计并不尽人意。于1996年发布的、描述HTTP 1.0规范的RFC 1945只有60页,但仅仅3年之后、描述HTTP 1.1规范的RFC 2616就一下增长到了176页。而当我们在IETF小组对该规范进行更新时,它更是被拆分成了总页数更多的六个文档(这就是RFC 7230及其文件族的由来与诞生)。总而言之,HTTP 1.1包含了太多细节和可选的部分,这让它变得过于庞大。

2.2 过多的可选项

HTTP 1.1不仅包含了非常多的细枝末节,同时也为未来的扩展预留了很多选项。这种事无巨细的风格导致在现有的软件生态中,几乎没有任何实现真正实现了协议中提及的所有细节,甚至要弄清楚“所有细节”到底包括哪些细节都非常困难。而这也导致了很多最初不常用的功能在后来的实现中很少会被支持,而有些最初实现了的功能,却又很少被使用。

随着时间推移,这些当初看似被边缘化的功能逐渐被用上,客户端和服务器的互用性(interoperability)问题就被暴露了出来。HTTP管线化(HTTP pipelining)就是一个非常好的例子。

2.3 未能被充分利用的TCP

HTTP 1.1很难榨干TCP协议所能提供的所有性能。HTTP客户端和浏览器必须要另辟蹊径的去找到新的解决方案来降低页面载入时间。

与此同时,人们也尝试去用新的协议来替代TCP,但结果证明这也非常困难。无奈之下,我们只能尝试同时改进TCP协议本身和基于TCP的上层协议。

简单来说,我们可以通过更好的利用TCP来减少传输过程中的暂停,并充分挖掘利用那些本可以用于发送/接受更多数据的时间。下面几段我们将会着重讨论这些问题。

2.4 传输大小和资源数量

如果仔细观察打开那些最流行的网站首页所需要下载的资源的话,会发现一个非常明显的趋势。 近年来加载网站首页需要的下载的数据量在逐渐增加,并已经超过了1.9MB。但在这里我们更应该关心的是:平均每个页面为了完成显示与渲染所需要下载的资源数已经超过了100个。

正如下图所示,这种趋势已经持续了很长一段时间,并且没有减缓的迹象。该图表中绿色直线展示了传输数据大小的增长,红色直线展示了平均请求资源数量的增长。

transfer size growth

2.5 恼人的延迟

HTTP 1.1对网络延迟非常敏感。部分原因是HTTP pipelining还存有很多问题,所以对大部分用户来说这项技术是被默认关闭的。

虽然近几年来网络带宽增长非常快,然而我们却并没有看到网络延迟有对应程度的降低。在高延迟的网络上(比如移动设备),即使拥有高连接速率,也很难获得优质快速的网络体验。

另外一个需要低延迟的场景是某些视频服务,如视频会议、游戏和一些类似无法预生成待发送数据流的服务。

2.6 线头阻塞(Head-of-line blocking)

HTTP pipelining是这样一种技术:在等待上一个请求响应的同时,发送下一个请求。(译者注:作者这个解释并不完全正确,HTTP pipelining其实是把多个HTTP请求放到一个TCP连接中一一发送,而在发送过程中不需要等待服务器对前一个请求的响应;只不过,客户端还是要按照发送请求的顺序来接收响应。)但就像在超市收银台或者银行柜台排队时一样,你并不知道前面的顾客是干脆利索的还是会跟收银员/柜员磨蹭到世界末日(译者注:不管怎么说,服务器(即收银员/柜员)是要按照顺序处理请求的,如果前一个请求非常耗时(顾客磨蹭),那么后续请求都会受到影响),这就是所谓的线头阻塞(head-of-line blocking)。

当然,你可以在选择队伍时候就做好功课,去排一个你认为最快的队伍,或者甚至另起一个新的队伍(译者注:即新建一个TCP连接)。但不管怎么样,你总归得先选择一个队伍,而且一旦选定之后,就不能更换队伍。

但是,另起新队伍会导致资源耗费和性能损失(译者注:新建 TCP 连接的开销非常大)。这种另起新队伍的方式只在新队伍数量很少的情况下有作用,因此它并不具备可扩展性。(译者注:这段话意思是说,靠大量新建连接是不能有效解决延迟问题的,即HTTP pipelining并不能彻底解决head-of-line blocking问题。)所以针对此问题并没有完美的解决方案。

这就是为什么即使到了今天,大部分桌面浏览器仍然会选择默认关闭HTTP pipelining这一功能的原因。

而关于这个问题的更多细节,可以参阅Firefox的 bugzilla #264354

3. 那些年,克服延迟之道

再困难的问题也有解决的方案,但这些方案却良莠不齐。

3.1 Spriting

Spriting是一种将很多较小的图片合并成一张大图,再用JavaScript或者CSS将小图重新“切割”出来的技术。

网站可以利用这一技巧来达到提速的目的——在HTTP 1.1里,下载一张大图比下载100张小图快得多。

但是当某些页面只需要显示其中一两张小图时,这种缓存整张大图的方案就显得过于臃肿。同时,当缓存被清楚的时候的时候,Spriting会导致所有小图片被同时删除,而不能选择保留其中最常用的几个。

3.2 内联(Inlining)

Inlining是另外一种防止发送很多小图请求的技巧,它将图片的原始数据嵌入在CSS文件里面的URL里。而这种方案的优缺点跟Spriting很类似。

.icon1 {
    background: url(data:image/png;base64,<data>) no-repeat;
  }
.icon2 {
    background: url(data:image/png;base64,<data>) no-repeat;
  }

3.3 拼接(Concatenation)

大型网站往往会包含大量的JavaScript文件。开发人员可以利用一些前端工具将这些文件合并为一个大的文件,从而让浏览器能只花费一个请求就将其下载完,而不是发无数请求去分别下载那些琐碎的JavaScript文件。但凡事往往有利有弊,如果某页面只需要其中一小部分代码,它也必须下载完整的那份;而文件中一个小小的改动也会造成大量数据的被重新下载。

这种方案也给开发者造成了很大的不便。

3.4 分片(Sharding)

最后一个我要说的性能优化技术叫做“Sharding”。顾名思义,Sharding就是把你的服务分散在尽可能多的主机上。这种方案乍一听比较奇怪,但是实际上在这背后却蕴藏了它独辟蹊径的道理!

最初的HTTP 1.1规范提到一个客户端最多只能对同一主机建立两个TCP连接。因此,为了不和规范冲突,一些聪明的网站使用了新的主机名,这样的话,用户就能和网站建立更多的连接,从而降低载入时间。

后来,两个连接的限制被取消了,现在的客户端可以轻松地和每个主机建立6-8个连接。但由于连接的上限依然存在,所以网站还是会用这种技术来提升连接的数量。而随着资源个数的提升(上面章节的图例),网站会需要更多的连接来保证HTTP协议的效率,从而提升载入速度。在现今的网站上,使用50甚至100个连接来打开一个页面已经并不罕见。根据httparchive.org的最新记录显示,在Top 30万个URL中平均使用40(!)个TCP连接来显示页面,而且这个数字仍然在缓慢的增长中。

另外一个将图片或者其他资源分发到不同主机的理由是可以不使用cookies,毕竟现今cookies的大小已经非常可观了。无cookies的图片服务器往往意味着更小的HTTP请求以及更好的性能!

下面的图片展示了访问一个瑞典著名网站的时产生的数据包,请注意这些请求是如何被分发到不同主机的。

image sharding at expressen.se

4. 升级HTTP

花点功夫去改善HTTP协议显然是极好的事情。我们可以着手于以下几个方面:

  1. 降低协议对延迟的敏感
  2. 修复pipelining和head of line blocking的问题
  3. 防止主机需求更高的连接数量
  4. 保留所有现有的接口,内容,URI格式和结构
  5. 由IETF的HTTPbis工作组来制定

4.1. IETF和HTTPbis工作组

The Internet Engineering Task Force (IETF)是一个开发和推广互联网标准的组织。他们的重心是在协议层面。他们最出名的工作是制定了TCP、DNS、FTP和它们最佳实践的RFC规范,但HTTP和许多其他协议却进展缓慢。

IETF成立了独立的“工作小组”以便完成某些特定领域内的目标,他们建立了一个“章程”用以制定达到目标的指导方针和规范。在这里,任何人都可以参与讨论和开发,并且每个人有同等的话语权,没人关心你来自哪个公司或组织。

HTTPbis工作组(我们待会儿再解释这个名字)在2007年夏天成立之后就着手于HTTP1.1标准的更新。在组内,关于下一版本HTTP协议的讨论实际上在2012年后期才开始。而HTTP1.1的更新工作在2014年初完成,并被整理成RFC 7320系列。

2014年6月初,HTTPbis工作组名义上的最终版文档会议在纽约召开。而剩下的讨论以及等IETF走完流程通过官方的RFC版本预计在来年完成。

一些HTTP领域的权威缺席了工作组的讨论和会议。我并不想在此提及任何公司和产品。但藉此,现在互联网上也有一些参与者因此获得了更多信心——不需要这些公司参与IETF也能做得很好。。。

4.1.1. 名字中的“bis”

工作组名字中的“bis”来自拉丁语中表示“二”的副词,Bis通常被IETF用作名字的后缀来以表示标准的升级或者一些二次工作,比如这里是针对HTTP1.1。

4.2. 起源于SPDY的http2

SPDY是由Google牵头开发的协议。他们将其开源,使得每个人都可以参与开发。但很明显,他们通过控制浏览器的实现和享用着优质服务的大量用户来获益。

当HTTPbis小组决定开始制定http2的时候,SPDY已经充分证实了它是一个非常好用的方案。当时已经有人在互联网上成功部署SPDY,并且也有一些文章讨论他的性能。因此,http2便基于SPDY/3草案进行一些修改之后发布了http2的draft-00。