HTTP 连接管理的演进

众所周知, 处于应用层的HTTP协议是建立在传输层 TCP 协议之上的, 这里主要介绍一下, 从旧版本的 HTTP 到最新版本的 HTTP里, HTTP 是以何种方式使用 TCP 协议传输数据的

HTTP进行数据传输依赖于 TCP 提供的客户端到服务端的连接, 在早期的 HTTP1.0版本, 每一个 HTTP 请求都会建立一个客户端到服务端 TCP 连接, 这时的连接模型被称为短连接, 这种连接模型无疑是非常浪费时间和资源的, 为了解决这个问题, HTTP在1.1版本加入了长连接流水线的连接模型, 这两种模型的会重复利用一个 TCP 连接, 大大地提高了请求的效率, 但是这两种连接也都有各自不足的地方, 所以在 HTTP/2里, 加入了多路复用,多路复用的模型, 更先进和高效, 下面我将这四个模型一一介绍.

短连接

短连接是 HTTP 最早期的连接模型, 在 HTTP0.9和 HTTP1.0版本里默认的连接模型, 对于每一个 HTTP请求, 都需要单独建立一个 TCP 请求, 请求结束以后, 这个 TCP连接将会关闭.

短连接]

这个连接模型的缺点是很明显的:

  1. 因为每次连接都需要进行握手, 所以一个每个请求需要两个RTT, 这意味着请求时延大.
  2. 频繁地打开关闭TCP连接对于服务端和客户端来说都会消耗相当多的CPU 和内存资源.

在 HTTP1.0中默认使用该连接模型, 在 HTTP1.1中当 Connection 被设置为 close 时, 才会使用该模型 ( 当然现在没人会用这个连接模型.

长连接

为了解决短连接的问题, 长连接的连接模型就被设计了出来, 在 HTTP1.1中默认使用长连接模型. 长连接的出现甚至在 HTTP1.1以前, 使用 connection: keep-alive头开启长连接.

客户端和服务端建立起 TCP 连接以后, 重复使用这一 TCP 连接进行一系列的 HTTP 请求和响应.

长连接

这样就节省了 TCP 握手的时间, 也不必频繁地打开和关闭连接, 当然这个 TCP 连接也不是一直打开的, 链接在空闲一段时间后会被关闭, 服务可以使用 keep-alive 协议头来指定一个最小的连接保持事件.

长连接也不是完美的, 在空闲状态会消耗服务器资源, 在重负载场景下, 应该尽快关闭空闲连接, 对性能会有提升.

流水线

在默认的情况下, HTTP请求是按顺序发出的, 下一个请求只有在当前请求收到响应以后才会发出. 在网络延迟较大, 或者带宽较小的情况下, 在下一请求到达服务器之前, 可能需要等待很长时间.

所以在 HTTP1.1中加入了流水线模型, 该模型会在同一 TCP 连接上发出连续的请求, 而不必等待响应, 服务器必须按照请求顺序按序做出相应.

流水线

这样做可以更高效地使用 TCP 连接, 甚至可能因为多个HTTP 连接被打包到一个 TCP 报文段中而提升性能.

但是由于队首阻塞的和重要消息会被延迟到非重要消息的后面的问题, 流水线模型在浏览器中是不被默认使用的.

域名分片

在上述的两个 HTTP1.1的模型中, 请求都是有序的, 为了更快获取到资源, 并充分利用带宽, 浏览器可以为每个域名建立多个 TCP 连接, 以实现并发的请求, 减少用户的等待时间.

并行TCP连接

这样就可以同时请求多个资源了.

现在比较常用的并发连接数是6条, 超过6条连接, 就可能触发服务器 Dos 保护. 所以, 每个浏览器对于一个域名来说, 最多建立6个 TCP 连接.

为了让浏览器更快的获取更多的资源, 我们可以通过将在一个域名拆分长多个域名, 以达到建立更多 TCP 连接并行请求资源的目的, 这个技术被称作域名分片. 例如 将www.foo.bar拆分成 ww1.foo.bar, ww2.foo.bar

将静态资源放到 CDN 上, 也利用了域名分片的技术, 既可以达到足够大的并行连接, 又能做缓存, 何乐而不为呢?

多路复用

为了解决 HTTP1.1两种连接模型的缺点, HTTP2中使用多路复用的连接模型.

多路复用的连接模型的实现, 主要得益于 HTTP2中二进制分帧的特性, 要讲清楚多路复用, 不得不先介绍HTTP2中二进制分帧流(stream)的概念.

流(stream)在 HTTP2中表示一次完整的资源请求 - 响应数据交换流程, 可以简单地将每个请求和其对应的相拥看做是一个流.

二进制分帧: HTTP2中, 将数据拆分成二进制数据帧, 这个二进制帧中包含了长度, 类型, 流标识符等信息, 以下是二进制帧的格式, 更多关于二进制分帧可以参考这里.

+-----------------------------------------------+
 |                 Length (24)                   |
 +---------------+---------------+---------------+
 |   Type (8)    |   Flags (8)   |
 +-+-------------+---------------+-------------------------------+
 |R|                 Stream Identifier (31)                      |
 +=+=============================================================+
 |                   Frame Payload (0...)                      ...
 +---------------------------------------------------------------+

*在传输过程中, *

*发送方 将不同 请求/响应 设置为不同的流, 再将不同的流拆分成二进制帧在一个 TCP 连接上传输, *

接收方根据二进制帧中的流标识符(Stream Identifier)把二进制帧组合成流(也就是资源)使用.

下图, 可将不同颜色的球视为不同的流:

二进制分帧

想必你看到这图就能理解多路复用的模型与之前的三个模型有何不同了.

在多路复用的模型相比前三种连接模型有以下优点:

  1. 请求无需按序发送 (这是针对不同的流而言的 , 同于一个流而言, 流内的数据需要按序到达), 减少了等待时间.
  2. 可以给不同的流设置优先级, 高优先级的流将更快被传输.
  3. 一个TCP 报文段中, 可以传输多个帧, 合并请求/响应在一个 TCP 段中可获得更高的性能.
  4. 二进制帧使用了二进制编码, 相比之前的版本传输更少的数据量.

HTTP2也不是没有缺点的, 比如太复杂了我实现不了 😂还有就是现在普及率不高, 但未来终将是 HTTP2的时代.

参考

HTTP/1.x 的链接管理 (mdn)

HTTP2 RFC: rfc7540

-- Ming