HTTP 2.0 将会使应用程序更快、更简单、并且更健壮。HTTP 2.0的首要目标通过请求-响应多路复用,报头压缩,优先级控制、服务器推送
来减少延时。为了达到这个目的,HTTP 2.0需要支持大量的路I量控制、错误异常处理以及协议升级机制。
HTTP 2.0 没有改变http1.x中methods, status codes, URIs, header fields这些元数据的语义。但是改变这些数据的传输方式。
HTTP 2.0 发展历史以及与SPDY关系
spdy 是google 在2009年中开发的实验性的协议,旨在通过解决HTTP 1.1中一些性能限制以降低网页的加载时间,它的大概要达到的目标有:
- 降低50%的页面加载时间
- 不要站点维护人员进行内容改变
- 简化部署复杂度,避免网络拓扑变化
- 与开源组织合作
- 搜集实验数据验证该协议
提出该协议后没多久,很快谷歌就公布是SPDY实验结果以及源码;
2012年,包括chrome,firefox opera等其他站点都以及支持了SPDY;同年,
HTTP Working Group已经准备将SPDY吸收到HTTP 2.0的官方标准中,之后HTTP-WG提出多次草案:
• March 2012: Call for proposals for HTTP 2.0
• September 2012: First draft of HTTP 2.0
• July 2013: First implementation draft of HTTP 2.0
• April 2014: Working Group last call for HTTP 2.0
• November 2014: Submit HTTP 2.0 to IESG as a Proposed Standard
设计和技术标准:
二进制帧
:
HTTP2 性能提升的核心是全新的二进制帧分层,客户端和服务端必须使用新的二进制编码方式同对端通信,下图描述了http消息在客户端和服务端间如何分装和传输:
二进制帧层只设计在socket收发层和HTTP层之间, 对http原来的语义,比如verbs,method和报头没有影响,但是在传输过程的编码方式不同,比如http1.x中使用的换行符,在http2通信中则被拆成更小的二进制消息和帧;
流,消息和帧
流:
一个tcp连接内的双向字节流;
消息:
一个对应着逻辑消息的完整的 一系列帧;
帧:
HTTP 2.0 最小的传输单位,每个帧都包含一个帧头,标识了每个帧属于哪个流;
HTTP2.0的通信是在一个tcp连接中进行的,每个连接可以包含人一个流。相应的,每个流通过消息通信,每个消息由帧组成,流之间可以相互交错并且通过帧头中的流标识进行重组。
HTTP 2.0 主要特征 总结:
- 所有通信只在一个TCP连接中进程
- 流在连接中是一个虚拟的通道,每个流携带者双向的信息。每个流程有着唯一的id (1, 2, …, N).
- 消息是一个逻辑的http消息,比如请求或者响应,它们由一个或者多个帧组成。
- 帧是最小的通信单位,携带者指定的数据,比如http报头或者负载等。
简单的说,HTTP 2.0将http协议拆分成了多个独立的帧,每个帧对应着一个逻辑的流。一个tcp连接中每个流可以并行的进行传输。
请求响应多路复用
HTTP/1.x 中,客户端可以通过使用多tcp连接让请求并行,但是请求是按顺序发送,同一个时间只能有一个响应返回。这种方式更不利的地方是,在http层产生了线头阻塞(只要某个响应卡住,其他相应也会卡住),另外也无法充分利用tcp的最大性能(每个连接都要通过慢启动方式来增大窗口来提高吞吐)
HTTP2.0 引入帧的概念消除了这个限制,通过将http消息拆成独立的、可交错的、可重组的帧,让所有的请求和响应都可以多路复用。
如上图,客户端发送一个
DATA frame (stream 5)给服务端,服务端为流1和流3发送一系列交错的帧给客户端。如此,就有三对 请求-响应 在并行的传输了。
将http消息拆分成了多个独立、可交错、可重组的帧,是http 2.0最大改进; 这些特性带来如下好处:
- 让请求和响应多路复用,而不用担心线头阻塞问题
- 只要用一个tcp连接就可以进行多个请求-响应通信,降低系统负载;
- 降低页面加载时间,减少不必要的延时
请求优先级
HTTP消息可以拆分成多个独立帧,帧发送的顺序将会影响应用程序的性能。为了控制帧顺序,每个流都可以赋予一个31位的优先级。
• 0表示着最高优先级
• 2^31 - 1 表示最低优先级
客户端和服务端可以应用不同的策略去用于控制流、消息和帧,服务器可以根据CPU、内存、带宽来标识优先级;如果响应数据已经到达,应该提升优先级发送给客户端。
单TCP连接
有了帧的特新,服务端和客户端只要保持一个长连接,就可以完成请求-响应多路复用,带来的好处是,减少socket的数量,减少内存使用,更大的tcp吞吐,减少网络拥塞,减少慢启动带来的影响。大多数的http消息都是简短的,突发的,只用一个连接能更充分使用tcp性能(但只使用一个连接的HTTP 2.0 ,不能缩短网络延时(受限光速),但能提高网络吞吐率以及减少不必要的消耗);
HTTP 2.0 使用单连接,也有一些潜在的缺点:
- 单连接解决了HTTP层的线头阻塞,但没有解决tcp层的线头阻塞(反而比多连接影响更大);
- 如果没有启用window scaling特性(窗口最大只有64k了),带宽延迟积将会限制连接吞吐率;
- 当发生丢包,拥塞窗口减小,连接的吞吐率会大大降低;
虽然性能过于依赖TCP和网络,但是实际实验过程中,使用单连接性能仍然比多连接好许多。HTTP 2.0解决了许多性能瓶颈,但是TCP将会是最后一个瓶颈。关于tcp优化的解决方案有
TCP Fast Open, Proportional Rate Reduction, increased initial congestion
window等等。
流量控制
单连接会引起多路复用产生对共享tcp资源的竞争,虽然优先级策略解决了资源的发送顺序,但不能很好解决多流之间的流控问题。因此,HTTP 2.0 引入了一个简单的机制来解决 多流间的流控问题。
- 流控是hop by hop,不是end to end
- 流控是基于window update帧,接收端通告对端在这条流或者整个链接中它将接收多少字节
- 流控通过WINDOW_UPDATE帧更新,其中指定了流id和窗口增大值
- 流控具有方向的,接收端可以为每个流选择设置任意大小窗口大小
- 流控可以被接收端禁止,可以是一条流也可以是整个连接。
当HTTP 2.0连接建立是,两端会交换setting帧,其中包含了窗口大小。当然任意一端也可以为某条流或者整个链接禁止流控功能。Tcp的窗口控制不能基于每个流来控制,这也是HTTP 2.0 需要流控的原因。
HTTP 2.0协议没有指定具体算法、值或者window update何时发送,实现中可以根据使用情况和性能来设置。
服务端推送
HTTP 2.0 有个强大功能,可以对一个请求发送除了对应的响应外,还会发送多个其他相关文件响应。多余的响应是和请求是相关的。服务端可以把请求的其他关的资源推送的客户端,而不用等待客户端一一请求。
服务端推送带来了一下几个好处:
- 推送资源可以被客户端缓存
- 推送资源可以被客户端拒绝
- 推送资源可以被多个页面复用
- 推送资源可以被服务端优先处理
PUSH_PROMISE
所有推送流都是通过
PUSH_PROMISE帧启动,
PUSH_PROMISE帧只是包含了推送资源的http报头。
客户端收到了推送帧,客户端可以选择拒绝该流(比如该资源已经被客户端缓存)。
服务端必须遵循请求-响应的规则,只有收到请求后才可以发送推送帧,而不能任意发送推送帧。推送内容必须在它本身请求之前被发送。
报头压缩
HTTP传输携带着一些列报头,这些报头描述了需要传输的资源以及资源的属性,在HTTP/1.x中这些语义都是用明文来表示并且这些报头让每个请求会增加500-800个字节大小,如果携带了cookies将会增加上千字节的数据。为了减少这些报头,并且提升性能,HTTP/2.0压缩了这些报头:
- 不用为每个请求和响应传输相同的数据,HTTP 2.0使用了 "header table",同时在服务端和客户端引用和存储这些key-value 对。
- Header table 在整个连接过程有效,客户端和服务端也可以进行增量更新。
- 一个新的key-value 可以添加到表的末端,也替换掉表中的旧值。
如此,收发两端就可以知道已经发送了那些报头以及对应的value,这样新报头就可以根据就得报头信息,发送不一样的键值对信息即可,如下图:
这个例子中,第二个请求只要发送发生改变的path报头,如此减少了重复报头发送量。一般情况下键值对在整个连接过程几乎不发生改变(如user-agent, accept header)。相同报头只要发送一次,如果报头都未发生变化,请求将继承之前请求的报头信息。
HTTP 2.0 升级和发现
切换到HTTP 2.0不可能在一夜之前发生,有数百万计的服务器,数亿计的客户端需要更新浏览器或者网络库。
http 2.0支持兼容模式的切换方式,客户端需要在连接启动阶段判断服务端是否支持HTTP 2.0; 需要考虑一下三种场景:
-
TLS 和ALPN方式启动https连接
- 序言方式启动http连接
- 没有序言方式启动连接
ALPN 用于发现HTTPS连接是否支持HTTP 2.0的TLS扩展头,可以在HTTPs连接握手阶段判断是否支持HTTP2.0,以减少延时。
明文方式启动的http将会费事些,HTTP/1.x 和HTTP 2.0都运行在80端口,没有其他信息可以用于判断是否支持HTTP2.0
客户端将会用一种 Upgrade 升级机制来协商出一种合适的协议:
GET /page HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: HTTP/2.0
(1)
HTTP2-Settings: (SETTINGS payload)
(2)
HTTP/1.1 200 OK
(3)
Content-length: 243
Content-type: text/html
(... HTTP 1.1 response ...)
(or)
HTTP/1.1 101 Switching Protocols
(4)
Connection: Upgrade
Upgrade: HTTP/2.0
(... HTTP 2.0 response ...)
- http 1.1请求携带 HTTP2.0升级报头
- HTTP2.0 SETTING携带 Base64编码的URL
- 服务器拒绝升级,返回http 1.1响应
- 服务器接受HTTP 2.0升级,切换到新框架
二进制帧简介
带长度表示的二进制帧是HTTP 2.0的核心改进点。与 HTTP/1.x使用换行分隔符相比,二进制帧提供了更加简洁的方式,代码上也更加简单和高效。一旦HTTP 2.0连接建立后,客户端和服务端通过帧来通信。每个帧都带有一个8字节的头部,包含了帧的长度和帧类型,一个标志位和一个31位的流id。
- 16位长度位,一个帧最大可以携带2^16 -1 长度数据,不包括8字节帧头
- 8位类型
- 8位标志位
- 一个保留位(一直为0)
- 31位流唯一标识
有以下几种帧类型:
DATA :数据帧用于传输HTTP body消息
HEADERS : 报头帧
由键值对(name-value)组成,用来打开一个流
PRIORITY : 优先级帧
明确了发送者建议的流的优先
RST_STREAM :终止帧用于终止一条流
SETTINGS :设置帧
设置两端通讯的参数
PUSH_PROMISE:
推送承诺帧
用来告知接收端准备稍后推送数据
PING :ping帧 用于衡量两端rtt
GOAWAY : 超时帧用于
通知接收端不要在当前连接上创建新流。但之前的流仍可以继续处理
WINDOW_UPDATE:
窗口更新帧
用来实现流量控制,包含对单个流或整个连接的控制,但只能影响DATA帧的传输
CONTINUATION:
延续帧
用来延续一个报头块片段。
启动一条新流
应用程序在发送数据前,必须创建一条流,指明优先级或者发送报头等。HTTP 2.0中,客户端和服务端都可以启动新流:
- 客户端通过发送报头帧启动一个请求,报头帧包含一个流ID,一个可选的31位优先级和一系列http报头键值对。
- 服务端通过发送一个承诺推送帧来启动一个推送流,几乎和报头帧一样,但多包含了一个承诺流的id,取代爆头战中的优先级值
为了避免流id冲突,客户端启动新流只取奇数id,服务端启动新流只取偶数id;流的元数据和应用程序数据是分开发送的,客户端和服务端可以使用不同的优先级,比如流控可以使用更高的优先级,流控只用于数据帧
数据发送
流一旦创建并且报头已经发送, http数据就可以发送了,数据可以拆成多个数据帧发送,最后一个数据帧打上
END_STREAM标记。
严格来说,数据帧长度可以达到2^16-1(
65535)
),但是为了避免线头阻塞, HTTP 2.0协议规定最大长度是2^14 -1 (
16383),超过最大值就要拆成多个数据帧。