文章同步于: Github/Blog
链接管理是一个 HTTP 的关键话题:打开和保持链接在很大程度上影响着网站和 Web 应用程序的性能。在 HTTP/1.x 里有好些个模型:短链接(short-lived connections)
,持久链接(persistent connections)
, 和HTTP 管道(HTTP pipelining)
。
HTTP 的传输协议主要依赖于 TCP 来提供从客户端到服务器端之间的链接。在早期,HTTP 使用一个简单的模型来处理这样的链接—— 短链接
。这些链接的生命周期是短暂的:每发起一个请求时都会建立一个新的链接,并在收到应答时当即关闭。html
这个简单的模型对性能有先天的限制:打开每个 TCP 链接都是至关耗费资源的操做。客户端和服务器端之间须要交换好些个消息。当请求发起时,网络延迟和带宽都会对性能形成影响。现代浏览器每每要发起不少次请求(十几个或者更多)才能拿到所需的完整信息,证实了这个早期模型的效率低下。java
有两个新的模型在 HTTP/1.1 诞生了。首先是长链接模型
,它会保持链接去完成屡次连续的请求,减小了不断从新打开链接的时间。而后是 HTTP Pipelining
,它还要更先进一些,多个连续的请求甚至都不用等待当即返回就能够被发送,这样就减小了耗费在网络延迟上的时间。git
要注意的一个重点是 HTTP 的链接管理适用于两个连续节点之间的链接,如 hop-by-hop,而不是 end-to-end。当模型用于从客户端到第一个代理服务器的链接和从代理服务器到目标服务器之间的链接时(或者任意中间代理)效果多是不同的。HTTP 协议头受不一样链接模型的影响,好比 Connection 和 Keep-Alive,就是 hop-by-hop 协议头,它们的值是能够被中间节点修改的。github
HTTP 最先期的模型,也是 HTTP/1.0 的默认模型,是短链接。每个 HTTP 请求都由它本身独立的链接完成;这意味着发起每个 HTTP 请求以前都会有一次 TCP 握手,并且是接二连三的。算法
TCP 协议握手自己就是耗费时间的,因此 TCP 能够保持更多的热链接来适应负载。短链接破坏了 TCP 具有的能力,新的冷链接下降了其性能。segmentfault
这是 HTTP/1.0 的默认模型(若是没有指定 Connection 协议头,或者是值被设置为 close)。而在 HTTP/1.1 中,只有当 Connection 被设置为 close 时才会用到这个模型。浏览器
短链接有两个比较大的问题:建立新链接耗费的时间尤其明显,另外 TCP 链接的性能只有在该链接被使用一段时间后(热链接)才能获得改善。为了缓解这些问题,持久链接(persistent connections)
的概念便被设计出来了,甚至在 HTTP/1.1 以前。或者这被称之为一个 keep-alive 链接。安全
HTTP/1.1(以及 HTTP/1.0 的各类加强版本)容许 HTTP 设备在事务处理结束以后将 TCP 链接保持在打开状态,以便为将来的 HTTP 请求重用现存的链接。在事务处理结束以后仍然保持在打开状态的 TCP 链接被称为持久链接。非持久链接会在每一个事务结束以后关闭。持久链接会在不一样事务之间保持打开状态,直到客户端或服务器决定将其关闭为止。服务器
一个 持久链接 会保持一段时间,重复用于发送一系列请求,节省了新建 TCP 链接握手的时间,还能够利用 TCP 的性能加强能力。固然这个链接也不会一直保留着:链接在空闲一段时间后会被关闭(服务器可使用 Keep-Alive 协议头来指定一个最小的链接保持时间)。网络
重用已对目标服务器打开的空闲持久链接,就能够避开缓慢的链接创建阶段。并且, 已经打开的链接还能够避免 慢启动 的 拥塞适应阶段,以便更快速地进行数据的传输。
持久链接也仍是有缺点的;就算是在空闲状态,它仍是会消耗服务器资源,并且在重负载时,还有可能遭受 DoS attacks 攻击。这种场景下,可使用非持久链接,即尽快关闭那些空闲的链接,也能对性能有所提高。
HTTP/1.0 里默认并不适用 持久链接。把 Connection 设置成 close 之外的其它参数均可以让其保持 持久链接,一般会设置为 retry-after
。
在 HTTP/1.1 里,默认就是持久链接的,协议头都不用再去声明它(但咱们仍是会把它加上,万一某个时候由于某种缘由要退回到 HTTP/1.0 呢)。
持久链接与并行链接配合使用多是最高效的方式。如今,不少 Web 应用程序都会打开少许的并行链接,其中的每个都是持久链接。
那些不理解 Connection 首部,并且不知道在沿着转发链路将其发送出去以前,应该将该首部删除的代理。不少老的或简单的代理都 是 盲中继(blind relay)
,它们只是将字节从一个链接转发到另外一个链接中去,不对 Connection 首部进行特殊的处理。
默认状况下,HTTP 请求是按顺序发出的。下一个请求只有在当前请求收到应答事后才会被发出。因为会受到网络延迟和带宽的限制,在下一个请求被发送到服务器以前,可能须要等待很长时间。
流水线是在同一条长链接上发出连续的请求,而不用等待应答返回。这样能够避免链接延迟。理论上讲,性能还会由于两个 HTTP 请求有可能被打包到一个 TCP 消息包中而获得提高。就算 HTTP 请求不断的继续,尺寸会增长,但设置 TCP 的 最大分段大小 MSS (Maximum Segment Size) 选项,任然足够包含一系列简单的请求。
并非全部类型的 HTTP 请求都能用到流水线:只有 idempotent 方式,好比 GET、HEAD、PUT 和 DELETE 可以被安全的重试:若是有故障发生时,流水线的内容要能被轻易的重试。
今天,全部遵循 HTTP/1.1 的代理和服务器都应该支持流水线,虽然实际状况中仍是有不少限制:一个很重要的缘由是,任然没有现代浏览器去默认支持这个功能。
HTTP 流水线在现代浏览器中并非默认被启用的:
- Web 开发者并不能轻易的碰见和判断那些搞怪的 代理服务器 的各类莫名其妙的行为。
- 正确的实现流水线式复杂的:传输中的资源大小,多少有效的 往返时延 RTT(Round-Trip Time) 会被用到,还有有效带宽,流水线带来的改善有多大的影响范围。不知道这些的话,重要的消息可能被延迟到不重要的消息后面。这个重要性的概念甚至会演变为影响到页面布局!所以 HTTP 流水线在大多数状况下带来的改善并不明显。
- 流水线受制于 队头阻塞 Head-of-line blocking (HOL blocking) 问题。
因为这些缘由,流水线已经被更好的算法给代替,如 multiplexing
,已经用在 HTTP/2。
在HTTP/2中,客户端向某个域名的服务器请求页面的过程当中,只会建立一条TCP链接,即便这页面可能包含上百个资源。而以前的HTTP/1.x通常会建立6-8条TCP链接来请求这100多个资源。单一的链接应该是HTTP2的主要优点,单一的链接能减小TCP握手带来的时延(若是是创建在SSL/TLS上面,HTTP2能减小不少没必要要的SSL握手,你们都知道SSL握手很慢)。
另外咱们知道,TCP协议有个滑动窗口,有慢启动这回事,就是说每次创建新链接后,数据先是慢慢地传,而后滑动窗口慢慢变大,才能较高速度地传,这下倒好,这条链接的滑动窗口刚刚变大,http1.x就创个新链接传数据(这就比如人家HTTP2一直在高速上一直开着,你HTTP1.x是一辆公交车走走停停)。因为这种缘由,让本来就具备突发性和短时性的 HTTP 链接变的十分低效。
因此,HTTP2中用一条单一的长链接,避免了建立多个TCP链接带来的网络开销,提升了吞吐量。
HTTP/2 是基于帧(frame)的协议。采用分帧是为了将重要信息都封装起来, 让协议的解析方能够轻松阅读、解析并还原信息。帧(frame)
是HTTP/2中数据传输的最小单位,所以帧不只要细分表达HTTP/1.x中的各个部份,也优化了HTTP/1.x表达得很差的地方,同时还增长了HTTP/1.x表达不了的方式。
HTTP/2 帧结构以下:
HTTP/2 规范对流(stream)的定义是:HTTP/2 链接上独立的、双向的帧序列交换。你能够将流看做在链接上的一系列帧,它们构成了单独的 HTTP 请求和响应。若是客户端想要发出请求,它会开启一个新的流。而后,服务器将在这个流上回复。这与 h1 的请求 / 响应流程相似,重要的区别在于,由于有分帧,因此多个请求和响应能够交错,而不会互相阻塞。流 ID(帧首部的第 6~9 字节)用来标识帧所属的流。
特色以下:
就是说在一个TCP链接上,咱们能够向对方不断发送一个个的消息,这里每个消息当作是一帧,而每一帧有个stream identifier
的字段标明这一帧属于哪一个 流
,而后在对方接收时,根据 stream identifier
拼接每一个 流
的全部帧组成一整块数据。咱们把 HTTP/1.x 每一个请求都看成一个 流
,那么请求化成多个流,请求响应数据切成多个帧,不一样流中的帧交错地发送给对方,这就是HTTP/2中的 多路复用
。
从上图咱们能够留意到:
多路复用让HTTP链接变得很廉价,只须要建立一个新流便可,这不须要多少时间,而在 HTTP/1.x 时代却要经历三次握手时间或者队首阻塞等问题。并且建立新流默认是无限制的,也就是能够无限制的并行请求下载。不过,HTTP/2 仍是提供了 SETTINGS_MAX_CONCURRENT_STREAMS
字段在 SETTINGS 帧
上设置,能够限制并发流数目,标准上建议不要低于 100 以保证性能。
实际的传输多是这样的:
只看到 帧(Frame)
,没有 流(Stream)
嘛。
须要抽象化一些,就好理解了:
HTTP/1.x
一次请求-响应,创建一个链接,用完关闭;每个小组任务都须要创建一个班级,多个小组任务多个班级,1:1比例HTTP/1.1 Pipeling
解决方式为,若干个小组任务排队串行化单线程处理,后面小组任务等待前面小组任务完成才能得到执行机会,一旦有任务处理超时等,后续任务只能被阻塞,毫无办法,也就是人们常说的线头阻塞HTTP/2
多个小组任务可同时并行(严格意义上是并发)在班级内执行。一旦某个小组任务耗时严重,但不会影响到其它小组任务正常执行