HTTP
队头阻塞 (Head-Of-Line Blocking)
队头阻塞主要是 TCP 协议的可靠性机制引入的。TCP 使用序列号来标识数据的顺序,数据必须按照顺序处理,如果前面的数据丢失,后面的数据就算到达了也不会通知应用层来处理。
- 单个数据流的发送:遇到错误的、丢失的包会出发重传,阻塞窗口移动
- 多个数据流的发送:TCP 通道里的流是混合的,不能独立的分割,所以当一个流阻塞时,其所有流都会被阻塞
解决:
- 将同一页面的资源分散到不同域名下,提升连接上限。 Chrome 有个机制,对于同一个域名,默认允许同时建立 6 个 TCP 持久连接,使用持久连接时,虽然能公用一个 TCP 管道,但是在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态。另外如果在同一个域名下同时有 10 个请求发生,那么其中 4 个请求会进入排队等待状态,直至进行中的请求完成。
- 减少请求次数
- 精灵图
- 图片转成 base64 嵌入到 css 文件中
- webpack 打包时将多个文件 合并成一个体积更大的
HTTP1.1 的改进
- 改进持久连接
长链接能够有效的减少 3 次握手和 4 次挥手过多导致的不必要开销 HTTP1.0 需要使用 Connection: keep-alive
参数来告诉服务器需要建立长链接,HTTP1.1 默认开启
- 不成熟的管线化技术
为了解决对队头阻塞,允许在第一个应答被完全发送之前就发送第二个请求,以降低通信延迟,但是服务器依然需要根据请求的顺序来恢复浏览器的请求,所以并不能彻底的解决这个问题,
- 提供虚拟主机支持
在 HTTP/1.0 中,每个域名绑定了一个唯一的 IP 地址,因此一个服务器只能支持一个域名。但是随着虚拟主机技术的发展,需要实现在一台物理主机上绑定多个虚拟主机,每个虚拟主机都有自己的单独的域名,这些单独的域名都公用同一个 IP 地址。 因此,HTTP/1.1 的请求头中增加了 Host 字段,用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理。
- 改进缓存机制
在 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 ID
与 Stream Offset
都一致,就说明这两个数据包的内容一致。
总结
- HTTP/1.1 有两个主要的缺点:安全不足和性能不高。
- HTTP/2 完全兼容 HTTP/1,是“更安全的 HTTP、更快的 HTTPS",头部压缩、多路复用等技术可以充分利用带宽,降低延迟,从而大幅度提高上网体验。
- QUIC 基于 UDP 实现,是 HTTP/3 中的底层支撑协议。该协议基于 UDP,又汲取了 TCP 中的精华,实现了既快又可靠的协议。