#前言
在开始长篇大论之前,让我们先看一个网络购物的例子。
有一天,客户端下了一个单子,服务端收到订单之后,给客户端发货,这个交易的过程,就是基于连接发生的请求与响应。

这个客户端,是个剁手党,一天要下好几个单子,可是在HTTP/1.0时代,连接无法复用,每次下完单,都被强制登出/关机,下一次下单,就得重新登录。
剁手党何其能忍?
其中一名剁手党看不下去了,作为一个程序猿,他决定改变这种现状,不然以后还怎么愉快地买键盘买主机买刀砍产品经理呢?
这个伟大的程序猿想到了一种优化方式:设置Connection:Keep-Alive,保持连接在一段时间内不断开。
HTTP/1.1默认开启Keep-Alive,但是,在keep-alive背景下,必须要等待订单1完成后,再继续处理订单2、3……这样的方式显然浪费时间,于是,万能的程序猿又想到了一个方法:HTTP pipelining。不等订单1结束,客户端就连续下了订单2、3、4……
(果然是剁手党之王 ……)

只可惜,仓库是按顺序发货的,若订单1的商品暂时没货,需要调货,订单2、3的商品要等订单1的商品发出之后才能发出。
为了解决这种情况,买家增加了好几个购物渠道(建立多个连接),某东、某会、某品……这样假如其中一个购物渠道阻塞了,其他渠道的订单可以不受影响……
但是,这还是不能完全解决问题:
购物渠道有限,最多只有6个;
- 每换一个购物渠道都得与客服沟通三次(TCP三次握手),既浪费时间,也会对卖家的服务端造成压力,同时容易受到环境影响而中途断开,需要再次重建;
- 随着订单的增多,多的订单还是只能按照先进先出(FIFO)的顺序进行排队,阻塞依旧很严重。
- 然而这一切都被机智的程序猿看在眼里,他们创造了一种SPDY协议,后续在此基础上,又起草了HTTP/2协议。相比于HTTP1.X,HTTP/2解决了许多问题:
多路复用
多路复用,即单个连接上同时进行多个业务单元数据的传输。
有了多路复用之后,在同一个交易渠道上,能够同时完成客户所有订单货物的采购和交付,客户端只要在每个订单上备注好ID,货物拆分发货,乱序到达之后按照ID重新组装即可,不会因为某个包裹的延误导致整体配送进度的推迟。

请求优先级
假如订单2的商品特别重要,就在订单2上留一段备注,服务端收到订单之后,会优先发出订单2的包裹。
同时,服务端评估订单5是短保产品,需要尽快到货,也会将订单5优先发货。

如此,一些比较重要的内容(如网页框架等)即可优先展示。
头部压缩
HTTP1.X的头部越来越膨胀,很多都是重复且多余的,HTTP/2可以压缩头部的大小,并且避免了重复的传输,可以大大降低延迟。
就好比货物越轻,运送速度则越快,HTTP/2协议下,卖家发货时将多余包装扔掉,这样买家就能更快地收到货啦!

服务端推送
服务端推送是HTTP/2的一大亮点。
在客户端下了订单1之后,服务端预先判断客户端可能会需要下订单2、3、4……于是主动发货。这种主动推送的机制,可以节省接下来的几个请求耗时,提升访问速度。

科普完毕的分割线
有了HTTP/2之后,卖家(网站)能够更快地将内容呈现给买家(用户)。

#1 HTTP/2是什么
##1.1 混沌之初
HTTP全称是超文本传输协议(HyperText Transfer Protocol) 。伴随着计算机网络和浏览器的诞生,HTTP/1.0也随之而来,处于计算机网络中的应用层,HTTP是建立在TCP协议之上,所以HTTP协议的瓶颈及其优化技巧都是基于TCP协议本身的特性,例如TCP建立连接的3次握手和每次建立连接带来的RTT延迟时间。
##1.2 矛盾凸显
###1.2.1 互联网的快速发展
- 古老的协议
HTTP建立之初,主要就是为了将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器。也是说对于前端来说,我们所写的HTML页面将要放在我们的web服务器上,用户端通过浏览器访问url地址来获取网页的显示内容。目前,互联网广泛使用的超文本传输协议(HTTP)是一个非常成功的协议,然而,HTTP 1.1更新时间是1999年(非常久远了),互联网发展日新月异,它的很多问题也渐渐暴露出来。 - WEB2.0的到来
但随着互联网的发展和web2.0的诞生,更多的内容开始被展示(更多的图片文件),排版变得更精美(更多的css),更复杂的交互也被引入(更多的js)。用户打开一个网站首页所加载的数据总量和请求的个数也在不断增加。今天绝大部分的门户网站首页大小都会超过2M,请求数量可以多达100个。 - 移动互联网时代的到来
当ajax的出现,我们又多了一种向服务器端获取数据的方法,这些其实都是基于HTTP协议的。同样到了移动互联网时代,我们页面可以跑在手机端浏览器里面,但是和PC相比,手机端的网络情况更加复杂,这使得我们开始了不得不对HTTP进行深入理解并不断优化过程中。
###1.2.2 矛盾成因
影响一个HTTP网络请求的因素主要有两个:带宽和延迟。
- 带宽:如果说我们还停留在拨号上网的阶段,带宽可能会成为一个比较严重影响请求的问题,但是现在网络基础建设已经使得带宽得到极大的提升,我们不再会担心由带宽而影响网速,那么就只剩下延迟了。
延迟成因:
- 浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,同时只能有 4~10 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制,后续请求就会被阻塞。
- DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用DNS缓存结果来达到减少这个时间的目的。
- 建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。
###1.2.3 矛盾现状
在HTTP/1.1发布之前,HTTP/1.0被抱怨最多的就是连接无法复用,和head of line blocking这两个问题。理解这两个问题有一个十分重要的前提:客户端是依据域名来向服务器建立连接,一般PC端浏览器会针对单个域名的server同时建立4~10个连接,手机端的连接数则一般控制在4~6个。显然连接数并不是越多越好,资源开销和整体延迟都会随之增大。
连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。
head of line blocking会导致带宽无法被充分利用,以及后续健康请求被阻塞。假设有5个请求同时发出,如下图:

对于HTTP/1.0的实现,在第一个请求没有收到回复之前,后续从应用层发出的请求只能排队,请求2,3,4,5只能等请求1的response回来之后才能逐个发出。网络通畅的时候性能影响不大,一旦请求1的request因为什么原因没有抵达服务器,或者response因为网络阻塞没有及时返回,影响的就是所有后续请求,问题就变得比较严重了。
解决连接无法复用
HTTP/1.0协议头里可以设置Connection:Keep-Alive。在header里设置Keep-Alive可以在一定时间内复用连接,具体复用时间的长短可以由服务器控制,一般在15s左右。到HTTP/1.1之后Connection的默认值就是Keep-Alive,如果要关闭连接复用需要显式的设置Connection:Close。
解决head of line blocking
Head of line blocking(以下简称为holb)是HTTP/2之前网络体验的最大祸源。正如上图所示,健康的请求会被不健康的请求影响,而且这种体验的损耗受网络环境影响,出现随机且难以监控。为了解决holb带来的延迟,协议设计者设计了一种新的pipelining机制。

即使以上两个问题有了解决方案,HTTP/1.1还是存在诸多问题:
- 只有幂等的请求(GET,HEAD)能使用pipelining,非幂等请求比如POST不能使用,因为请求之间可能会存在先后依赖关系。
- head of line blocking并没有完全得到解决,server的response还是要求依次返回,遵循FIFO(first in first out)原则。也就是说如果请求1的response没有回来,2,3,4,5的response也不会被送回来。
- HTTP/1.1在使用时,header里携带的内容过大,在一定程度上增加了传输的成本,并且每次请求header基本不怎么变化。
- 虽然HTTP/1.1支持了keep-alive,来弥补多次创建连接产生的延迟,但是keep-alive使用多了同样会给服务端带来大量的性能压力,并且对于单个文件被不断请求的服务(例如图片存放网站),keep-alive可能会极大的影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间。
##1.3 应运而生
###1.3.1 SPDY 大放异彩
2012年google如一声惊雷提出了SPDY的方案,大家才开始从正面看待和解决老版本HTTP协议本身的问题。SPDY可以说是综合了HTTPS和HTTP两者有点于一体的传输协议,主要解决:
- 降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing)。多路复用通过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了带宽的利用率。
- 请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,这样重要的请求就会优先得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能第一时间看到网页内容。
- header压缩。前面提到HTTP1.x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大小和数量。
- 基于HTTPS的加密协议传输,大大提高了传输数据的可靠性。
- 服务端推送(server push),采用了SPDY的网页,例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。
SPDY位于HTTP之下,TCP和SSL之上,这样可以轻松兼容老版本的HTTP协议(将HTTP1.x的内容封装成一种新的frame格式),同时可以使用已有的SSL功能。

###1.3.2 HTTP/2主角登场
HTTP/1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,而HTTP/1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP/1.1也是当前使用最为广泛的HTTP协议。HTTP/2即超文本传输协议 2.0,是下一代HTTP协议。是由互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis (HTTPbis)工作小组进行开发。是自1999年HTTP/1.1发布后的首个更新。

HTTP/2可以说是SPDY的升级版(其实原本也是基于SPDY设计的),但是,HTTP/2 跟 SPDY 仍有不同的地方,主要是以下两点:
- HTTP/2 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
- HTTP/2 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATE
#2 HTTP/2的新特性
##2.1 二进制分帧层
HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,例如:如空白字符、大小写、行尾、空行等的处理,二进制则不同,只认0和1的组合。HTTP/2将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码 ,其中HTTP1.x的首部信息会被封装到Headers帧,而我们的request body则封装到Data帧里面。
然后,HTTP/2 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。相应地,每个数据流以消息的形式发送,而消息由一或多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装。

###2.1.1 帧通用格式
帧头为固定的9个字节((24+8+8+1+31)/8=9)呈现,变化的为帧的负载(payload),负载内容是由帧类型(Type)定义。
- 帧长度Length:无符号的自然数,24个比特表示,仅表示帧负载所占用字节数,不包括帧头所占用的9个字节。默认大小区间为为0~16,384(2^14),一旦超过默认最大值2^14(16384),发送方将不再允许发送,除非接收到接收方定义的SETTINGS_MAX_FRAME_SIZE(一般此值区间为2^14 ~ 2^24)值的通知。
- 帧类型Type:8个比特表示,定义了帧负载的具体格式和帧的语义,HTTP/2规范定义了10个帧类型,这里不包括实验类型帧和扩展类型帧
- 帧的标志位Flags:8个比特表示,服务于具体帧类型,默认值为0x0。
- 帧保留比特位:1个比特表示,在HTTP/2语境下为保留的比特位。
- 流标识符:无符号的31比特表示无符号自然数。0x0值表示为帧仅作用于连接,不隶属于单独的流。
###2.1.2 帧类型
规范定义了10个正式使用到帧类型,扩展实验类型的ALTSVC、BLOCKED等不在介绍之列。 - SETTINGS 设置帧,接收者向发送者通告己方设定,服务器端在连接成功后必须第一个发送的帧。
- HEADER 报头主要载体,请求头或响应头,同时也用于打开一个流,在流处于打开”open”或者远程半关闭”half closed (remote)”状态都可以发送。
- CONTINUATION 用于协助HEADERS/PUSH_PROMISE等单帧无法包含完整的报头剩余部分数据。
- DATA 一个或多个DATA帧作为请求、响应内容载体。
- PUSH_PROMISE 服务器端通知对端初始化一个新的推送流准备稍后推送数据。
- PING 优先级帧,类型值为0x6,8个字节表示。发送者测量最小往返时间,心跳机制用于检测空闲连接是否有效。
- PRIORITY 优先级帧,类型值为0x2,5个字节表示。表达了发送方对流优先级权重的建议值,在流的任何状态下都可以发送,包括空闲或关闭的流。
- WINDOW_UPDATE 流量控制帧,作用于单个流以及整个连接,但只能影响两个端点之间传输的DATA数据帧。但需注意,中介不转发此帧。
- RST_STREAM 优先级帧,类型值为0x3,4个字节表示。表达了发送方对流优先级权重的建议值,任何时间任何流都可以发送,包括空闲或关闭的流。
- GOAWAY 一端通知对端较为优雅的方式停止创建流,同时还要完成之前已建立流的任务。
##2.2 多路复用
多路复用,即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。
多路复用带来的好处:
- 可以减少服务链接压力,内存占用少了,连接吞吐量大了
- 由于 TCP 连接减少而使网络拥塞状况得以改观;
- 慢启动时间减少,拥塞和丢包恢复速度更快。
多路复用原理图:

##2.3 首部压缩
首部压缩,HTTP/2 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;通信期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型,等等)只 需发送一次。事实上,如果请求中不包含首部(例如对同一资源的轮询请求),那么 首部开销就是零字节。此时所有首部都自动使用之前请求发送的首部。如果首部发生变化了,那么只需要发送变化了数据在Headers帧里面,新增或修改的首部帧会被追加到“首部表”。首部表在 HTTP/2 的连接存续期内始终存在,由客户端和服务器共同渐进地更新 。
##2.4 并行双向字节流
并行双向字节流的请求和响应,在HTTP/2上,客户端和服务器可以把HTTP 消息分解为互不依赖的帧,然后乱序发送,最后再在另一端把它们重新组合起来。注意,同一链接上有多个不同方向的数据流在传输。客户端可以一边乱序发送stream,也可以一边接收者服务器的响应,而服务器那端同理。
事实上,这个机制会在整个 Web 技术栈中引发一系列连锁反应, 从而带来巨大的性能提升,因为:
- 可以并行交错地发送请求,请求之间互不影响;
- 可以并行交错地发送响应,响应之间互不干扰;
- 只使用一个连接即可并行发送多个请求和响应;
- 消除不必要的延迟,从而减少页面加载的时间;
##2.5 取消数据流
数据流发送到一半的时候,客户端和服务器都可以发送信号(RST_STREAM帧),取消这个数据流。1.1版取消数据流的唯一方法,就是关闭TCP连接。这就是说,HTTP/2 可以取消某一次请求,同时保证TCP连接还打开着,可以被其他请求使用。
##2.6 服务端推送
服务端推送(server push),就是服务器可以对一个客户端请求发送多个响应。除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求。
当服务端需要主动推送某个资源时,便会发送一个 Frame Type 为 PUSH_PROMISE 的 Frame,里面带了 PUSH 需要新建的 Stream ID。意思是告诉客户端:接下来我要用这个 ID 向你发送东西,客户端准备好接着。客户端解析 Frame 时,发现它是一个 PUSH_PROMISE 类型,便会准备接收服务端要推送的流。
##2.7 请求优先级
请求优先级,HTTP/2允许浏览器指定资源的优先级。每个HTTP/2流里面有个优先值,这个优先值确定着客户端和服务器处理不同的流采取不同的优先级策略,高优先级的流都应该优先发送,但又不会绝对的。绝对地遵守,可能又会引入首队阻塞的问题:高优先级的请求慢导致阻塞其他资源交付。分配处理资源和客户端与服务器间的带宽,不同优先级的混合也是必须的。
#3 注意事项
##3.1 升级
HTTP/2 协议本身并没有要求它必须基于 HTTPS(TLS)部署,但是出于以下三个原因,实际使用中,HTTP/2 和 HTTPS 几乎都是捆绑在一起:
- HTTP 数据明文传输,数据很容易被中间节点窥视或篡改,HTTPS 可以保证数据传输的保密性、完整性和不被冒充;
- 正因为 HTTPS 传输的数据对中间节点保密,所以它具有更好的连通性。基于 HTTPS 部署的新协议具有更高的连接成功率;
- 当前主流浏览器,都只支持基于 HTTPS 部署的 HTTP/2;
对比HTTPS的升级改造,HTTP/2获取会稍微简单一些,你可能关注以下问题:
- 前文说了HTTP/2其实可以支持非HTTPS的,但是现在主流的浏览器像chrome,firefox表示还是只支持基于 TLS 部署的HTTP/2协议,所以要想升级成HTTP/2还是需要先升级为HTTPS。
- 当你的网站已经升级HTTPS之后,那么升级HTTP/2就简单很多,如果你使用NGINX,只要在配置文件中启动相应的协议就可以了,可以参考NGINX白皮书,NGINX配置HTTP/2官方指南。
- 使用了HTTP/2,原本的HTTP1.x怎么办,这个问题其实不用担心,HTTP/2完全兼容HTTP1.x的语义,对于不支持HTTP/2的浏览器,NGINX等WEB服务器会自动向下兼容的。
##3.2 抛弃针对 HTTP/1.x 的优化
- 资源合并和内容内嵌
资源合并(如:Spriting)和内容内嵌(如:图片Base64)其本身的目的是通过减少请求数量,达到优化的目的。而HTTP/2的多路复用,让通过减少请求数量的优化方式不在需要。 - 域名分片
域名分片本身解决的问题是浏览器对于单个域名的server同时建立6~8个连接,手机端的连接数则一般控制在4~6个。域名分片本身的目的是通过多域名的方式突破这个限制,达到优化的目的。而在HTTP/2上,把 HTTP 消息分解为独立的帧,交错发送。客户端可以一边乱序发送stream,也可以一边接收者服务器的响应,而服务器那端同理。也就是说“域名分区”这种优化手段对于HTTP/2是无用的,因为资源都是并行交错发送,且没有限制,不需要额外的多域名并行下载。
##3.3 常见问题
为什么修订 HTTP ?
HTTP/1.1 已经很好地服务 Web 超过 15 个年头,但它的缺点开始显现出来。
载入一个 Web 页面相比之前会占用更多的资源(详情可见HTTP压缩页大小统计),高效的载入这些资源很难,因为 HTTP 实际上对每个 TCP 连接,只允许一个优先的 HTTP 请求。
在过去,对于并发请求,浏览器使用多个 TCP 连接。然而这也是有局限的;如果使用了过多的连接,则会有相反的效果(TCP 的流控机制导致发送窗口乘性递减,从而产生的拥塞事件影响了性能和网络的表现),同时从根本上来讲这也是不公平的(因为浏览器承载的资源需求大于它们享有的网络资源)。
此外,大量的请求意味着“线上”有很多重复的数据。
HTTP/1.1 在这两个问题上消耗了大量的资源。如果发起过多的请求,则会影响性能。
这些问题导致了像雪碧图、数据内联、域共享和文件合并有了最佳的实践场合。这些技巧正是底层协议问题的表现,并且在使用中也导致了它们自身的一系列问题。
谁创造了 HTTP/2 ?
HTTP/2 由 IETF 的 HTTP 工作组开发,他们也在维护着 HTTP 协议。他们由一群 HTTP 实现者、用户、网络运营商和 HTTP 专家组成。
注意虽然我们的邮件列表托管在 W3C 的站点上,并工作并不是他们所承担。然而,Tim Berners-Lee 和 W3C TAG 与 WG 的进度保持了一致。
很多人对相关工作作出了贡献,不过大部分活跃的参与者都来自于像 Firefox、Chrome、Twitter、Microsoft 的 HTTP stack、Curl 和 Akamai 这样“大”项目的工程师,以及若干 Python、Ruby 和 NodeJS 的 HTTP 实现者。
要了解更多 IETF 的参与者,请看 IETF 之道。你也可以在 Github 的贡献者图表中了解到哪些人正在对规范做着贡献,以及在我们的实现列表中了解哪些人正在参与实现。
与 SPDY 有什么关联?
HTTP/2 第一次出现并被讨论的时候,SPDY 正得到厂商 (像 Mozilla 和 nginx)的青睐,并被看成是基于 HTTP/1.x 的重大改进。
经过提议和投票流程之后,SPDY/2 被选为 HTTP/2 的基础。直从那时起,根据工作组的讨论和厂商的反馈,它已经有了很多变化。
在整个过程中,SPDY 的核心开发成员都参与了 HTTP/2 的发展,其中包括了 Mike Belshe 和 Roberto Peon。
在 2015 年 2 月份,Google 发表了放弃 SPDY 转而支持 HTTP/2 的声明。
是 HTTP/2.0 还是 HTTP/2 ?
工作组决定去掉小版本 (“.0”) ,因为它在 HTTP/1.x 中造成了很多困惑。
也就是说, HTTP 的版本仅仅代表它的线上兼容性,而不表示它的特性集合或者“市场吸引力”。
与 HTTP/1.x 有什么关键区别?
宏观上来讲,HTTP/2:
- 基于二进制而不是文本的
- 完全多路复用,代替原来的排序和阻塞机制
- 在一条连接中并行处理多个请求
- 压缩头部减少开销
- 允许服务器主动推送响应到客户端的缓存中
为什么 HTTP/2 是二进制的?
比起像 HTTP/1.x 这样的文本协议,二进制协议解析起来更高效、“线上”更紧凑,更重要的是错误更少,因为它对如空白字符、大小写、行尾、空行等的处理都更有效。
例如,HTTP/1.1 定义了四个不同的方法来解析一条消息;在HTTP/2中,仅需一个代码路径即可。
HTTP/2 在 telnet 中将不可用,但是我们有一些工具提供支持,比如 Wireshark 插件。
为什么 HTTP/2 是多路复用的?
HTTP/1.x 有个问题叫队头阻塞,即一个连接同时只能有效地承载一个请求。
HTTP/1.1 试过用流水线来解决这个问题,但是效果并不理想(数据量较大或者速度较慢的响应,仍然会阻碍排在后面的响应)。此外,由于网络中介和服务器都不能很好的支持流水线技术,导致部署起来困难重重。
客户端被迫使用一些启发式的算法(基本靠猜)来决定哪些连接来承载哪些请求;由于通常一个页面加载资源的连接需求,往往超过了可用连接资源的 10 倍,这对性能产生极大的负面影响,后果经常是引起了风暴式的阻塞。
而多路复用则能很好的解决这些问题,因为它能同时处理多个消息的请求和响应;甚至可以在传输过程中将一个消息跟另外一个糅合在一起。
所以客户端只需要一个连接就能加载一个完整的页面。
为什么只用一个 TCP 连接?
在 HTTP/1 下,浏览器为每个域分配了 4 到 8 个连接。因为许多站点使用多个域,因此一般一个页面会加载 30 多个连接。
一个应用同时打开这么多连接,已经远远超出了当初设计 TCP 时的预期;由于每一个连接都会在响应中开启一个数据洪流,中介网络的缓存存在溢出的风险,结果导致网络堵塞和数据重传。
此外,使用这么多连接还会强占许多网络资源。这些资源都是从脾气好的其它应用那“偷”来的 ( VoIP 就是个例子)。
服务器推送的收益是什么?
当浏览器请求一个网页时,服务器将会发回 HTML,在服务器开始发送 JavaScript、图片和 CSS 前,服务器需要等待浏览器解析 HTML 并发起所有内嵌资源的请求。
服务器推送服务通过“推送”那些它认为客户端将会需要的内容到客户端的缓存中,以此来避免往返的延迟。
为什么需要压缩头部?
来自 Mozilla 的 Patrick McManus 通过计算头部对页面负载的平均影响度,清晰地说明了这个问题。
假设一个页面中有大约 80 个资源(对于今天的 Web 来说这个数字是比较保守的),并且每个头部有 1400 比特(同样并不罕见,多谢 Cookies、Referer 等),这就需要至少七八个往返传递头部。这还没有计算响应时间——那只是客户端获取它们所花的时间而已。
这是由于 TCP 的慢启动机制造成的,在新的连接上发送数据包的速度取决于有多少个数据包已经被确认——在最初的几轮中这有效限制了可以发送的数据包的数量。
作为对比,即使是轻微的头部压缩也可以是让那些请求只需一个来回就能搞定——有时候甚至一个数据包就可以了。
这种开销是可以被节省下来的,特别是当你考虑移动端应用的时候,即使是条件良好的情况下,一般也会看到几百毫秒的往返延迟。
为什么使用 HPACK ?
SPDY/2 曾提出在每个方向上都使用一个单独的 GZIP 上下文用于消息头的压缩,这实现起来很容易,也很高效。
从那时候开始,一种针对流压缩(如 GZIP)中加密算法的重要攻击方式被发现:CRIME。
通过 CRIME,攻击者有能力在加密流中注入数据以“探测”文本内容并恢复它。由于是在 Web 环境中,JavaScript 使之成为了可能,并且,已经有在受 TLS 保护的 HTTP 资源中利用 CRIME 恢复 cookies 和鉴权令牌的先例。
因此,我们不能使用 GZIP 压缩。也没有找到其它安全和合适的压缩算法,我们就创造了一种新的、针对 HTTP 消息头的粗略压缩方案;由于 HTTP 的消息头在消息之间并不经常变化,因此我们可以得到合理的压缩效率,并且更加地安全。
HTTP/2 可以让 cookies(或其它头部)更好吗?
HTTP/2 是对现有运行的协议的修订,包括如何升级新的 HTTP 头部、方法,而不会影响 HTTP 的语义。
因为 HTTP 被使用得如此广泛,如果我们在这个版本中引入了一个新的状态机制(例如之前讨论过的例子)或者改变了核心方法(幸好没有人提起这件事),那么就意味着新的协议和现在的协议发生了不兼容。
特别要强调,我们需要的是无缝地从 HTTP/1 过渡到 HTTP/2。如果我们现在开始“清算”头部(大多数人会认同,HTTP消息头现在简直是一团糟),那么我们也就不得不面对现代 Web 中的互操作性问题。
那样做只会在采用新协议的过程中制造麻烦。
总而言之,工作组会对所有的 HTTP 负责,而不仅仅只是 HTTP/2。 因此,只要同现有的网络兼容,我们才可以独立于版本地运行新的机制。
非浏览器形式的 HTTP 用户该怎么办?
非浏览器用户如果已经在使用 HTTP,那么也可以使用 HTTP/2。
之前收到过 HTTP/2 针对 HTTP “API” 有更好的性能特点的报告,因为 API 在设计时不需要考虑像请求开销这样的问题。
曾提到过,我们认为 HTTP/2 改进的重点是典型的浏览器环境,因为这是该协议的主要应用场景。
我们的章程里面是这样说的:
最终的标准期望能针对已经部署的普通 HTTP 达到这些目标;
特别地,
包括 Web 浏览器(桌面和移动端)、非浏览器(“HTTP API”),
网络服务(不同尺度的)、中介(代理、企业防火墙,反向代理以及内容分发网络)。
同样,当前和未来针对 HTTP/1.x (头部、方法、状态码、缓存指令)的语义化扩展
也应该在新的协议中被支持。注意这并不涵盖 HTTP 的非标准用法(比如连接状态的
超时、客户端关联以及拦截代理);它们都不会被最终启用。
HTTP/2 需要加密吗?
不需要。经过广泛的讨论,工作组对于新的协议在加密(如 TLS)的使用上没有达成共识。
但是,在实现上厂商都已表明他们只会在加密连接下才会支持 HTTP/2,目前,没有浏览器支持不加密的 HTTP/2。
HTTP/2 该如何提升安全性?
HTTP/2 定义了一个所需 TLS 的基本描述信息;包括版本、密码套件和用到的扩展。
细节参见相关规范。
此外也有额外的相关讨论,比如对 HTTP:// URL(所谓的“机会主义加密”)使用TLS;参见issue #315。
现在可以使用 HTTP/2 了吗?
在浏览器上,Edge、Safari、Firefox 和 Chrome 的近期版本都支持 HTTP/2。其它使用 Blink 内核的浏览器(Opera 和 Yandex)也都支持 HTTP/2。
此外还有几个可用的服务器(包括 Akamai 的 beta 版本,Google 和 Twitter 的主站),以及一些你可以部署和测试的开源实现。
详情请看实现列表。
HTTP/2 会替换 HTTP/1.x 吗?
工作组的目标是 HTTP/1.x 的典型应用能够使用 HTTP/2 并且看到收益。之前提到,我们不能强制大家迁移,因为人们部署代理和服务器的方式让 HTTP/1.x 在未来还会使用一段时间。
未来会有 HTTP/3 吗?
如果 HTTP/2 的协商机制工作良好,未来会更容易升级到新的 HTTP 版本。
##3.4 实现问题
为什么规则会围绕头部帧的数据接续?
数据接续的存在是由于一个值(如 Set-Cookie)可以超过 16kb - 1,这意味着它不可能全部装进一个帧里面。所以就决定以最不容易出错的方式让所有的消息头数据以一个接一个帧的方式传递,这样就使得对消息头的解码和缓冲区的管理更加的容易。
HPACK状态的最小和最大尺寸是多少?
接收一方总是会控制 HPACK 中内存的使用量,并且最小能设置到 0,最大则要看 SETTING 帧中能表示的最大整型数是多少,目前是 2^32 - 1。
我怎样才能避免保持 HPACK 状态?
发送一个 SETTINGS 帧将状态尺寸 (SETTINGS_HEADER_TABLE_SIZE) 设置到 0,然后 RST 所有的流,直到一个带有 ACT 设置位的 SETTINGS 帧发送了过来。
为什么会有一个单独的压缩/流控制上下文?
原来的提案中有流分组的概念,它会共享上下文和流控等。那样有利于代理 (也有利于使用它们的用户的体验),而这样做相应也会增加一点复杂度。所以我们就决定先以一个简单的东西开头,看看它会带来多糟糕的问题,并且在未来的协议版本中解决这些问题(如果有的话)。
在 HPACK 中为什么会有一个 EOS 符号?
考虑到 CPU 的效率和安全,HPACK 的哈夫曼编码填充了哈夫曼编码字符串到下一个字节边界。因此对于任何特定的字符串可能需要 0-7 个比特的填充。
如果单独考虑哈夫曼解码,任何比所需要的填充长的符号都可以正常工作。但是,HPACK 的设计允许按字节对比哈夫曼编码的字符串。通过填充 EOS 符号需要的比特,我们确保用户在做哈夫曼编码字符串字节级比较时是相等的。反过来许多头部可以在不需要哈夫曼解码的情况下被解析。
我可以实现 HTTP/2 而不实现 HTTP/1.1 吗?
基本上是可以的。
对于运行在 TLS (h2) 之上的 HTTP/2 而言,如果你没有实现 http1.1 的 ALPN 标识,那你就不需要支持任何 HTTP/1.1 特性。
对于运行在 TCP (h2c) 之上的 HTTP/2 而言,你需要实现最初的升级(Upgrade)请求。
只支持 h2c 的客户端将需要生成一个请求 “*” 的 OPTIONS 请求或者请求 “/” 的 HEAD 请求,它们绝对安全,并且也很容易构建。要实现 HTTP/2 的客户端将只需要把没有带上 101 状态码的 HTTP/1.1 响应看做是一个错误就行了。
只支持 h2c 的服务器可以使用一个固定的 101 响应来接收一个包含升级(Upgrade)消息头字段的请求。没有 h2c 的 Upgrade 令牌的请求可以使用一个包含了 Upgrade 消息头字段的 505(HTTP版本不支持)状态码来拒绝。那些不希望处理 HTTP/1.1 响应的服务器,应该在发送了带有鼓励用户升级到 HTTP/2 以重试的连接引导之后,立即用带有 REFUSED_STREAM 的错误码拒绝该请求的第一份数据流。
我的 HTTP/2 的连接还需要 TCP_NODELAY 吗?
是的,可能需要。即使对于那种只需要使用单个连接下载大量数据的客户端实现,一些数据包仍然需要反向发回以达到最大的传送速度。没有 TCP_NODELAY(但仍然允许 Nagle 算法),要发出去的数据包可能会被阻塞一会儿,以达到与后续数据包合并的目的。
如果有这样一个数据包,它的目的是告诉对方仍有增加发送窗口的空间,延迟它的发送几百毫秒(或更多)将会对高速连接产生负面的影响。
##3.5 部署问题
我如何调试加密的 HTTP/2 ?
用许多方式可以访问应用的数据,最简单的方法是使用 NSS keylogging 并配合 Wireshark 的插件(最近的基本开发版本)。这对 Firefox 和 Chrome 都有效。
我如何使用 HTTP/2 的服务器推送?
HTTP/2 的服务器推送允许在不等待客户端请求的情况下向客户端提供内容。这可以节省请求的时间开销,特别是对于大型高延迟带宽的产品,它的网络交互时间主要消耗在资源上。
基于请求的内容推送不同的资源可能是不合适的。当前,浏览器只有在发起一个匹配请求(参见 RFC 7234 第四部分)时才会使用推送的请求。
一些缓存并不对所有请求头都响应变化。即使被列在了 Vary 头中。为了最大限度地提高推送资源的利用率,最好避免内容协商。基于 accept-encoding 的内容协商广泛被缓存认可,但是其它头部则不太会被支持。
#4 大势所趋
浏览器的支持情况:目前已经在很多Web浏览器和服务器中得到实现。大约有三分之二的浏览器已经支持HTTP/2,而且这个比例每月都在增加。

截至2016年12月,前1000万个网站中,有10.8%支持HTTP/2,其中自然少不了Google、Twitter等行业先驱。
国内网站中,百度、豆瓣、知乎、QQ邮箱、携程、搜狐、蘑菇街及部分直播平台等已经开始用HTTP/2。
#延伸
参考资料:
https://ye11ow.gitbooks.io/http2-explained/content/part1.html