Skip to content

HTTP

队头阻塞 (Head-Of-Line Blocking)

队头阻塞主要是 TCP 协议的可靠性机制引入的。TCP 使用序列号来标识数据的顺序,数据必须按照顺序处理,如果前面的数据丢失,后面的数据就算到达了也不会通知应用层来处理。

  • 单个数据流的发送:遇到错误的、丢失的包会出发重传,阻塞窗口移动
  • 多个数据流的发送:TCP 通道里的流是混合的,不能独立的分割,所以当一个流阻塞时,其所有流都会被阻塞

解决:

  • 将同一页面的资源分散到不同域名下,提升连接上限。 Chrome 有个机制,对于同一个域名,默认允许同时建立 6 个 TCP 持久连接,使用持久连接时,虽然能公用一个 TCP 管道,但是在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态。另外如果在同一个域名下同时有 10 个请求发生,那么其中 4 个请求会进入排队等待状态,直至进行中的请求完成。
  • 减少请求次数
    • 精灵图
    • 图片转成 base64 嵌入到 css 文件中
    • webpack 打包时将多个文件 合并成一个体积更大的

HTTP1.1 的改进

  1. 改进持久连接

长链接能够有效的减少 3 次握手和 4 次挥手过多导致的不必要开销 HTTP1.0 需要使用 Connection: keep-alive 参数来告诉服务器需要建立长链接,HTTP1.1 默认开启

  1. 不成熟的管线化技术

为了解决对队头阻塞,允许在第一个应答被完全发送之前就发送第二个请求,以降低通信延迟,但是服务器依然需要根据请求的顺序来恢复浏览器的请求,所以并不能彻底的解决这个问题,

  1. 提供虚拟主机支持

在 HTTP/1.0 中,每个域名绑定了一个唯一的 IP 地址,因此一个服务器只能支持一个域名。但是随着虚拟主机技术的发展,需要实现在一台物理主机上绑定多个虚拟主机,每个虚拟主机都有自己的单独的域名,这些单独的域名都公用同一个 IP 地址。 因此,HTTP/1.1 的请求头中增加了 Host 字段,用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理。

  1. 改进缓存机制

在 HTTP1.0 中主要使用 header 里的 If-Modified-Since(比较资源最后的更新时间是否一致),Expires(资源的过期时间(取决于客户端本地时间)) 来做为缓存判断的标准。 HTTP1.1 则引入了更多的 缓存控制 策略:

  • Entity tag:资源的匹配信息
  • If-Unmodified-Since:比较资源最后的更新时间是否不一致
  • If-Match:比较 ETag 是否一致
  • If-None-Match:比较 ETag 是否不一致

等更多可供选择的缓存头来控制缓存策略。

HTTP1.1 - 缺陷

  • 高延迟 — 队头阻塞

  • 无状态,协议开销大

    没有相应的压缩传输优化方案。 HTTP/1.1 在使用时,header 里携带的内容过大,在一定程度上增加了传输的成本,并且每次请求 header 基本不怎么变化,尤其在移动端增加用户流量。

  • 明文传输 — 不安全性

  • 不支持服务端推送

HTTP2 的改进

  • 二进制传输

  • Header 压缩

    • 减小 header 的体积
    • 在客户端服务端建立字典,通过 “首部表” 跟踪之前的数据,对于相同的数据不再发送
  • 多路复用

    HTTP2.0 中,有两个概念非常重要:帧(frame)和流(stream)。 帧是最小的数据单位,每个帧会标识出该帧属于哪个流,流是多个帧组成的数据流。 所谓多路复用,即在一个 TCP 连接中存在多个流,即可以同时发送多个请求,对端可以通过帧中的表示知道该帧属于哪个请求。在客户端,这些帧乱序发送,到对端后再根据每个帧首部的流标识符重新组装。通过该技术,可以避免 HTTP 旧版本的队头阻塞问题,极大提高传输性能。

  • 服务端推送

    主动向客户端发送消息。比如,在浏览器刚请求 HTML 的时候就提前把可能会用到的 JS、CSS 文件发给客户端,减少等待的延迟

  • 更安全

    HTTP2.0 使用了 TLS 的拓展 ALPN 做为协议升级,除此之外,HTTP2.0 对 tls 的安全性做了近一步加强,通过黑名单机制禁用了几百种不再安全的加密算法。

HTTP2 - 缺陷

  • 丢包下性能差

    TCP 为了保证传输的可靠性,有个 丢包重传 的机制,也就是丢包后整个 TCP 通道的请求都需要重新请求。HTTP1.1 时我们可以创建多个 TCP 通道,一个有问题不会影响到其他的,但是 HTTP2.0 所有的请求都在一个通道,所以丢包下性能会差

TIP

在 TCP 里,如果一个 segment 传递丢失,那么后续 segment 乱序到达,也不会被应用层使用,只到丢失的 segment 重传成功为止,因此 TCP 实现的 HTTP2 的多路复用能力受到制约

  • 多路复用导致服务器压力变大

    多路复用会让所有请求同时发送,所以会造成请求的短暂爆发,导致服务器压力增加

  • 多路复用容易 timeout

HTTP3

1. 使用 QUIC(Quick udp internet connection) 协议

  • 底层使用 UDP 传输, UDP 是 “无连接” 的,因此不需要 “握手、挥手”,故速度很快
  • 虽然 UDP 不提供可靠性的传输,但 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性。
  • 集成了 TLS 加密功能。
  • 多路复用,彻底解决 TCP 中队头阻塞的问题(QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流,实现了数据流的独立传输)。
  • 实现动态可插拔,在应用层实现了拥塞控制算法,可以随时切换。应用程序层面就能实现不同的拥塞控制算法,不需要操作系统,不需要内核支持。这是一个飞跃,因为传统的 TCP 拥塞控制,必须要端到端的网络协议栈支持,才能实现控制效果。而内核和操作系统的部署成本非常高,升级周期很长,这在产品快速迭代,网络爆炸式增长的今天,显然有点满足不了需求。
  • 报文头和报文体分别进行认证和加密处理,保障安全性。

QUIC 怎么保证数据的可靠性

QUIC 使用的 Packet Number 单调递增的设计,可以让数据包不再像 TCP 那样必须有序确认,QUIC 支持乱序确认,当数据包 Packet N 丢失后,只要有新的已接收数据包确认,当前窗口就会继续向右滑动。待发送端获知数据包 Packet N 丢失后,会将需要重传的数据包放到待发送队列,重新编号比如数据包 Packet N+M 后重新发送给接收端,对重传数据包的处理跟发送新的数据包类似,这样就不会因为丢包重传将当前窗口阻塞在原地,从而解决了队头阻塞问题。那么,既然重传数据包的 Packet N+M 与丢失数据包的 Packet N 编号并不一致,我们怎么确定这两个数据包的内容一样呢?

QUIC 使用 Stream ID 来标识当前数据流属于哪个资源请求,这同时也是数据包多路复用传输到接收端后能正常组装的依据。重传的数据包 Packet N+M 和丢失的数据包 Packet N 单靠 Stream ID 的比对一致仍然不能判断两个数据包内容一致,还需要再新增一个字段 Stream Offset,标识当前数据包在当前 Stream ID 中的字节偏移量。

有了 Stream Offset 字段信息,属于同一个 Stream ID 的数据包也可以乱序传输了(HTTP/2 中仅靠 Stream ID 标识,要求同属于一个 Stream ID 的数据帧必须有序传输),通过两个数据包的 Stream IDStream Offset 都一致,就说明这两个数据包的内容一致。

总结

  • HTTP/1.1 有两个主要的缺点:安全不足和性能不高。
  • HTTP/2 完全兼容 HTTP/1,是“更安全的 HTTP、更快的 HTTPS",头部压缩、多路复用等技术可以充分利用带宽,降低延迟,从而大幅度提高上网体验。
  • QUIC 基于 UDP 实现,是 HTTP/3 中的底层支撑协议。该协议基于 UDP,又汲取了 TCP 中的精华,实现了既快又可靠的协议。

参考