蛮荆

HTTP1 到 HTTP3 的工程优化

2023-03-18


HTTP/1.0

存在的问题

  1. 默认为短连接,连接无法复用,网页中的每个资源都会发起新的 TCP 连接
  2. 队列头部请求阻塞 (head of line blocking), 一个 HTTP 请求响应结束之后,才能发起下一个 HTTP 请求 (如果没有某个特别慢的请求,就卡顿了 …)
  3. 不支持范围数据请求,即使只是需要某个资源的一部分内容 (例如视频的某一段帧),也会将整个资源发送过来

这几个问题已经全部消失在历史长河中了,这里简单回顾下,不做详细介绍了。

HTTP/1.1

首先增加了以下特性解决了 HTTP/1.0 存在的问题:

  • 默认启用长连接
  • 支持同时打开多个 TCP 连接,采用 Pipeline 请求方式,多个请求可以通过多个连接串行化请求
  • 支持资源分块范围数据传输

此外,还新增了以下新特性:

  • 支持虚拟主机
  • 新增 Cache-Control、E-Tag, max-age 缓存处理指令
  • 新增 PUT、PATCH、HEAD、OPTIONS、DELETE 请求方法

依然存在的问题

  • 虽然 TCP 连接可以复用,但是服务端响应只能按照客户端请求顺序返回,队列头部请求阻塞 (head of line blocking) 并没有完全解决
  • 客户端需要使用多个连接才能实现并发和缩短延迟
  • 无法压缩请求和响应头部,导致不必要的数据传输流量
  • 不支持有效的资源优先级,导致 TCP 连接的利用率低

HTTP/2

HTTP/1 是文本协议,其中 Header 头信息是文本数据,内容体数据可以是文本格式,也可以是二进制格式。HTTP/2 是二进制协议,Header 头信息和内容体数据都是二进制的。

二进制分帧层

HTTP/2 在 应用层(HTTP/2)和传输层(TCP or UDP)之间增加一个二进制分帧层。在不改动 HTTP/1.1 的语义、方法、状态码、URI 以及头部字段的情况下, 解决了HTTP1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量。

图片来源: https://hpbn.co/http2/

HTTP/2 将一个 HTTP 请求划分为 3 个部分:

  1. 帧 (Frame) : 一段二进制数据,是 HTTP/2 传输的最小单位,每个帧包含一个帧头,用于标识该帧所属的流,来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装数据
  2. 消息 (Message) : 和请求或响应对应的多个帧序列
  3. 流 (Stream) : 已建立的连接内的双向数据字节流,可以承载一条或多条消息,每个流都有一个唯一标识符和可选的优先级信息,由客户端发起的流必须使用奇数编号作为标识符;由服务器发起的必须使用偶数编号作为标识符,流标识符零(0x0)用于连接控制消息

图片来源: https://hpbn.co/http2/

请求/响应 多路复用

图片来源: https://blog.cloudflare.com/http-2-for-web-developers/

在整个通信过程中,只会有一个 TCP 连接存在,它承载了任意数量的双向数据流 (Stream)。

图片来源: https://hpbn.co/http2/

如图所示,客户端正在向服务端传输 stream 5,服务端正在向客户端交替传输 stream 1 和 stream 3, 因此存在 3 个并行的流 (3 个流位于双向的一条 TCP 连接上面)。

这种单连接多资源的方式,减少服务端的链接压力 (主要是握手和开启 HTTPS 后的验证), 内存占用更少 (连接队列), 连接吞吐量更大;而且由于 TCP 连接的减少而使网络拥塞状况得以改善 (减少 TCP 三次握手、开启 HTTPS 后的 TLS 握手), 同时慢启动时间的减少,使拥塞和丢包恢复速度更快 (因为当发生丢包时,TCP拥塞窗口大小会因为拥塞避免机制而减小,从而降低整个连接的最大吞吐量)。

HTTP/2 通过二进制分帧层,可以缓解 HTTP/1.1 中依然存在的 队列头部请求阻塞 (head of line blocking) 和解决 客户端需要建立多个 TCP 连接 问题。

💡 该方案的本质是多路复用,这里的「多路」指多个资源请求,「复用」指在同一个 TCP 连接上传输。

在 HTTP/1.1 协议中浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞。 这也是为何一些站点会有多个静态资源 CDN 域名的原因之一,而 HTTP/2 的多路复用(Multiplexing) 则允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。 因此 HTTP/2 可以很容易地去实现多流并行而不用依赖建立多个 TCP 连接,HTTP/2 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。并行地在同一个 TCP 连接上双向交换消息。

为什么只有一个 TCP 连接?

在 HTTP/1.1 协议中浏览器打开单个域名网站可能会使用多个连接 (实现多站点传输),结果就是单个站点页面加载时会打开数十个 TCP 连接。 一个应用程序打开如此多的 TCP 连接,这已经远远超出了最初的 TCP 设计理念,而且由于每个 TCP 连接都会响应大量的数据,会增加网络缓冲区的溢出风险,导致网络拥塞事件并重新开始数据传输。

图片来源: https://halfrost.com/http2_begin/

HTTP/2 通过单个 TCP 连接,完全消除了 HTTP/1.1 中创建多个 TCP 连接带来的性能开销 (TCP + TLS)。

请求优先级

多个 HTTP 请求同时发送时,会产生多个数据流,每个数据流中有一个优先级的标识,服务器端可以根据这个标识来决定响应的优先顺序。

  1. 每个数据流可以分配一个 1 到 256 之间的整数作为其权重值
  2. 每个数据流可以被指定对另一个数据流的显式依赖

对于浏览器来说,并非所有资源都拥有同样的优先级 (例如大多数情况下 HTML 文件应该比 CSS 文件拥有更高的优先级),为了加速页面访问,现代浏览器都会根据资源的具体类型和在页面上的位置进行优先级排序, 甚至会根据历史访问响应时间记录来学习优先级,例如某个资源在之前访问时被阻塞了,那么这个资源在将来的访问中会获得更好的优先级。

通过设置合理的请求优先级,可以有效缓解 队列头部请求阻塞 (head of line blocking) 和 TCP 连接的利用率低 两个问题。 默认情况下,浏览器的优先级机制已经优化的足够好,无需在代码层面设置资源优先级。

服务端推送

HTTP/2.0 在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。

例如客户端请求 page.html 页面,服务端顺带着就把 script.js 和 style.css 等相关的资源一起发给客户端。

HTTP/2 Server Push

推送的资源有如下特点:

  • 可以被客户端 (一般指浏览器) 缓存
  • 可以被不同的页面进行复用
  • 可以被服务端确定优先级
  • 客户端完全控制服务端的推送行为: 限制并发推送流的数量、调整初始流量控制窗口、控制流首次打开时推送的数据量、或者完全禁用服务端推送,这些首选项在 HTTP/2 建立连接时通过设置帧进行传递,并且可以随时更新
  • 每个被推送的资源都是一个流,这允许客户端单独对其进行多路复用、优先级排序和数据处理,当然,推送的资源必须遵守同源策略

PUSH_PROMISE 帧

服务端的所有推送都是通过 PUSH_PROMISE 帧发起的,它表示服务端会将资源推送到客户端,并且允许服务端在客户端请求之前就发送相关资源给客户端。 这些资源可能是客户端未直接请求的,但服务端认为客户端可能会需要的资源。通过推送可以避免客户端发起额外的请求来获取这些资源,从而加快页面加载速度。 PUSH_PROMISE 帧包含了推送资源的相关信息,如资源的 URL、HTTP 头部等,客户端接收到 PUSH_PROMISE 帧,可以选择拒绝流 (通过 RST_STREAM 帧),例如资源已经存在于客户端的缓存中。

HPACK 压缩

HTTP/2 要求客户端和服务器同时维护和更新一个包含之前见过的头部字段表,从而避免了重复传输。 HTTP/2 中通信双方各自缓存一份头部字段表,如:把 Content-Type:text/html 存入索引表中,后续如果要用到这个头,只需要发送对应的索引号就可以了。

通过头部压缩,解决了 HTTP/1.1 中头部重复导致的不必要的数据传输问题。

HTTP/2 Header Compress

作为进一步的优化,HPACK 压缩上下文 由静态和动态表组成:

  • 静态表中定义了规范标准,主要提供连接可能会使用到的常见 HTTP 报头字段 (例如 host, path, method …)
  • 动态表初始时为空,但是会根据特定连接内的不同字段值进行更新,通过对新出现的字段值使用 Huffman 编码,以及对 客户端/服务端 双方静态表或动态表中已经存在的字段值更新索引,可以减少每个请求的大小

HTTP/2 Header Compress

流控制

流控制是一种发送方与接收方之间的协商机制,防止双方向对方发送大量数据时,造成对方负载过重,或者限制特定资源的流量速率。 例如客户端请求了一个高优先级的大视频流,但是用户观看几秒后暂停了,此时客户端应该暂停或限制其从服务器端的数据传输,避免请求和缓冲不必要的数据。

本质上这是一个流量控制问题,也许你会想到 TCP 中的流量控制机制,但是 HTTP/2 是使用单个 TCP 连接进行多路复用的,这样一来, TCP 传输层流量控制既没有足够的流量控制粒度 (没有办法以 HTTP 请求资源为粒度进行控制,因为得不偿失,会直接浪费掉多路复用带来的所有好处), 也没有必要提供应用层的 API 来控制单个流的传输控制。为了解决这个问题,HTTP/2 提供了一组简单的构建块,允许客户端和服务端实现自己的连接控制和流控制。

HTTP/2 流控制具体的规则如下:

  • 流控制是定向的,接收方 (客户端/服务端都是彼此的接收方) 可以选择为每个流和整个连接设置窗口大小
  • 流控制是基于窗口机制的,接收方会其初始连接和流控制窗口(以字节为单位),当发送方发出数据帧时,该窗口就会减少,当发送方接收到来自接收方发送的 WINDOW_UPDATE 帧时,该窗口就会增加
  • 无法禁用流控制,当建立 HTTP/2 连接时,客户端和服务器交换 SETTINGS 帧,用来设置两个方向的流量控制窗口大小,流量控制窗口的默认值设置为 65535 字节, 最大窗口大小为(2^31 - 1 字节),并通过在接收到数据时发送 WINDOW_UPDATE 帧来更新

HTTP/2 没有指定任何特定的算法来实现流量控制,它仅提供了简单的构建块,并将实现委托给客户端和服务器,客户端和服务器可以使用它实现自定义策略调节资源分配。 应用层流量控制允许浏览器只获取特定资源的一部分,通过将流量控制窗口 (WINDOW_UPDATE) 减少到零来实现暂停资源获取,然后在合适的时间再进行恢复。 例如获取图像的预览图 (该图像内容的一部分),显示预览图的同时允许其他高优先级的请求继续获取,并在优先级更高的资源完成加载后恢复继续图像的获取请求。

一次 HTTP/2 通信示例

HTTP/2 通信过程

  1. 首次访问时,浏览器请求头部加上 upgrade: h2c 标识,声明客户端支持 HTTP/2,询问服务器要不要更换协议
  2. 浏览器同时发送 HTTP/2-Settings 头部,带上 base64 编码的 SETTINGS frame
  3. 对于 HTTPS 请求,是在 TLS 握手阶段进行协商,浏览器发送 ClientHello 时,带上 h2 标志,表明客户端支持 HTTP/2
  4. 如果服务器不支持,则忽略 upgrade 头部,正常响应。如果支持,则发送101响应,以空行结束响应,并开始发送 HTTP/2帧
  5. 服务器要先响应 connection preface,带上 SETTINGS frame
  6. 服务器创建新流,推送 a.js。然后继续发送 index.html 文件和 a.js 文件的response header、response body
  7. 浏览器收到 PUSH_PROMISE 帧,发现服务器要推送的内容已经在浏览器缓存里了,发送 RST_STREAM 拒绝推送
  8. 服务器收到 RST_STREAM 帧后,不再推送 a.js 文件剩余的数据
  9. 服务器想要关闭连接,发送 GOAWAY 帧

检测是否支持 HTTP/2

通过 CURL 命令来检测网站是否支持 HTTP/2 协议。

$ curl -I "https://dbwu.tech"

# 输出如下

HTTP/2 200 
date: Sun, 22 Jan 2023 04:15:29 GMT
content-type: text/html; charset=utf-8
...

直接使用 –http/2 参数指定 CURL 请求使用 HTTP/2 协议。

$ curl --http/2 "https://dbwu.tech"

也可以通过 在线工具 进行检测。

HTTP/2 在线检测工具

HTTP/1 升级后过时的优化方案

升级到 HTTP/2 之后,很多 HTTP/1 中的优化方案,在 HTTP/2 中就没有存在的必要了,例如下面这些曾经的 “经典” 优化方案:

  • Sprites: 将很多小图合并成一张大图,再利用 CSS 和 JavaScript 将小图定位并切割出来 (常见的业务场景如 Logo 图集、网页游戏道具等)

图片来源: https://www.formget.com/css-image-sprites

  • Inlining: 将图片的原始数据 base64 编码之后嵌入到 CSS 属性中 (可以节约一次 HTTP 请求)

图片来源: https://stackoverflow.com/questions/41762836/what-is-base-64-fonts-for-css

  • Concatenation: 将许多小的静态文件合并到一个大的静态文件中

图片来源: https://blog.cloudflare.com/http-2-for-web-developers/

图片来源: https://blog.cloudflare.com/http-2-for-web-developers/

  • Sharding: 将静态资源分发到不同的域名 (即使没有 HTTP/2, 这个方案也可以使用 CDN 来替代,方案的核心在于并发 TCP 连接以及优化 cookies)

图片来源: https://blog.cloudflare.com/http-2-for-web-developers/

HTTP/1 升级后仍然有效的的优化方案

除了上述升级到 HTTP/2 失效的优化方案外,大部分在 HTTP/1 优化的方案在 HTTP/2 中仍然有效吗,例如下面这些方案:

  • 降低 DNS 轮询 (可以参考主流云计算厂商提供的解决方案及实现原理)
  • 使用 CDN (并且根据业务场景和服务进行设计拆分)
  • 尽可能使用浏览器缓存机制
  • 尽可能优化请求响应时间和响应内容大小
  • 尽可能消除重定向

HTTP/2 的优化空间

HTTP/2 针对基于 TCP 协议栈的 HTTP 优化,性能几乎已经达到了最大化,如果需要继续深入优化,只能从协议栈本身的架构做调整,当然也就是 HTTP/3 协议主要做的工作。

优化核心目标依然是 TCP 的可靠性机制导致的传输效率和延迟问题:

  • TCP 建立连接时的三次握手,增加了请求延时
  • TCP 内部的拥塞控制、慢启动、拥塞避免等机制,可能导致传输效率不足
  • HTTP/2 虽然 (在应用层) 解决了 HTTP/1 协议中的 队列头部请求阻塞 (head of line blocking) 问题,但在 (传输层) TCP 协议也存在类似的问题: TCP 在传输时使用序列号标识数据的顺序,一旦某个数据丢失,后面的数据需要等待这个数据重传后才能进行下一步处理
  • 根据测试表明,在较差的网络环境中 (丢包率 >= 2%),HTTP/2 的性能甚至不如 HTTP/1, 因为 HTTP/1 一般会打开多个 TCP 连接,即使其中一个或多个连接出现丢包,剩下的连接依然可以进行数据传输

HTTP/3

HTTP/3 是基于 QUIC(Quick UDP Internet Connections)协议的新一代 HTTP 协议。

QUIC

QUIC 是由 Google 提出的基于 UDP 进行多路复用的传输协议,是一个 UDP 版的 TCP + TLS + HTTP/2 替代方案实现。 QUIC 没有连接的概念,不需要三次握手,在应用程序层面,实现了 TCP 的可靠性,TLS 的安全性和 HTTP2 的并发性。 在设备支持层面,只需要客户端和服务端的应用程序支持 QUIC 协议即可,无操作系统和中间设备的限制。

QUIC 丢掉了 TCP 的包袱,基于 UDP,实现了一个安全高效可靠的 HTTP 通信协议。凭借着 0-RTT 建立连接、传输层多路复用、连接迁移、改进的拥塞控制、流量控制等特性,QUIC 在绝大多数场景下获得了比 HTTP/2 更好的效果。

HTTP/2 和 HTTP/3 的协议栈比较

题外话

为什么不发明一个新的传输协议

事实上,面对传统传输层 TCP 和 UDP 协议的各种问题和不足,创新型的传输层协议最终都没有能成为行业标准。因为这不单单是技术问题, 客户端与服务端之间要经过网络中的运营商防火墙、路由器、NAT 等,这些设备中很多默认只支持 TCP 和 UDP 协议,那么可想而知,新型协议根本无法在互联网普及。 而且,即使上述所有的中间设备想要支持新型协议,那么更新和部署新的网络协议栈、操作系统内核等基础设施和软件,必然是一个十分缓慢的过程,在此期间造成的停机等问题引起的经济损失可能是无法估量的。

核心优化

最重要的优化就是使用 QUIC 协议代替了 HTTP/2 中的依赖的 TCP 协议栈。

0-RTT

QUIC 协议可以实现 0-RTT 建立连接 (0-RTT 是指通信双方发起通信连接时,第一个数据包就可以携带有效的业务数据),而 TCP 需要 3-RTT 建立连接,这个优势不止体现在初始建立连接时,在网络发生变化时同样适用。

图片来源: https://en.wikipedia.org/wiki/QUIC

关于 0-RTT,需要说明的是: 如果客户端和服务器是第一次通信,那么需要经过 1-RTT (主要是客户端获取服务端加密配置),如果已经有过一次通信之后, 后续客户端和服务端的通信连接就是 0-RTT。限于篇幅,第一次客户端连接到服务端获取密钥及加密配置的过程,本文不再展开描述。

多路复用

图片来源: https://www.debugbear.com/blog/http3-quic-protocol-guide

QUIC 中的每个 stream 之间是相互独立的,单个 stream 丢失了,不会影响到其他 stream,QUIC 协议在发送数据时会拆分为多个包, 这样就完全解决了 队列头部请求阻塞 (head of line blocking) 问题。 尽管 QUIC 消除了 HTTP/2 的队列头部请求阻塞问题,但其依赖的 UDP 本身是无序交付的,也就是数据不一定按照发送时的顺序到达, (所以并不是切换到 HTTP/3 就万事大吉了,客户端和服务端必须要根据实际业务场景,尝试做更多的优化工作)。

单调递增的序列号

TCP 中,每一个数据包都有一个序列号标识(seq),如果接收端超时没有收到,就会要求重发标识为 seq 的包,如果此时恰好接受到了超时的包, 则无法区分哪个是超时的包,哪个是重传的包。

  • RTT: Round Trip Time, 往返事件
  • RTO: Retransmission Timeout, 超时重传时间

如果客户端认为收到的包是重传包,但是实际是超时的包,这样就会导致计算出来的 RTT 值偏小,反之计算出来的 RTT 值偏大。

示例: RTT 值偏小

在上面的示例图中,RTT 计算出来的 RTT 值比实际值要小。

示例: RTT 值偏大

在上面的示例图中,RTT 计算出来的 RTT 值比实际值要大。

QUIC 中的每一个包的标识(Packet Number)都是单调递增的,重传的序号一定大于超时的序号,这样就能有效地区分超时和重传。

禁止 Reneging

TCP 中,如果接收方内存不够或 Buffer 溢出,则可能会把已接收的包丢弃,这种行为对数据重传产生了很大的干扰,在 QUIC 中是明确禁止的。在 QUIC 中,一个包只要被 ACK ,就认为一定会被正确接收。

批量 ACK

TCP 中每收到 3 个数据包就要返回一个 ACK,而 QUIC 最多可以收到 256 个包之后,才返回 ACK。在丢包率比较严重的网络下,更多的 ACK 块可以减少重传量,提升网络效率。

ACK Delay

TCP 计算 RTT 时没有考虑接收方接收到数据到发送确认消息之间的延迟,也就是所谓的 ACK Delay。QUIC 充分考虑到 ACK Delay,这样 RTT 的计算会更加准确。

图片来源: https://assets.extrahop.com/whitepapers/TCP-Optimization-Guide-by-ExtraHop.pdf

流量控制

TCP 通过滑动窗口来控制流量,如果某一个包丢失了,滑动窗口并不能跨过丢失的包继续滑动,而是会卡在丢失的位置,等待数据重传后,才能继续滑动。

QUIC 流量控制的核心是:不能建立太多的连接,以免响应端处理不过来;不能让某一个连接占用大量的资源,让其他连接没有资源可用。 为此 QUIC 流量控制分为 连接级别和 Stream 级别 :

  • Stream 级别流量控制中,接收窗口 = 最大接收窗口 - 已接收数据
  • 连接级别流量控制中,接收窗口 = Stream1 接收窗口 + Stream2 接收窗口 + … + StreamN 接收窗口

连接迁移

TCP 连接是由(源 IP,源端口,目的 IP,目的端口)组成,这个四元组中一旦有一项值发生改变,这个连接也就不能用了。如果我们从 wifi 网络切换到 4G 网络,IP 地址就会改变,这个时候 TCP 连接也自然断掉了。

QUIC 使用客户端生成的 64 位 ID 来表示一条连接,只要 ID 不变,这条连接也就一直维持着,不会中断。

前向纠错机制

QUIC 使用前向纠错(FEC,Forward Error Correction)技术增加协议的容错性。

图片来源: https://www.xoriant.com/blog/quic-is-around-it-is-being-tested-on-you

如图所示,一段数据被切分为 10 个包后,依次对每个包进行异或运算,运算结果会作为 FEC 包与数据包一起被传输,如果不幸在传输过程中有一个数据包丢失, 那么就可以根据剩余 9 个包以及 FEC 包推算出丢失的那个包的数据,这样就大大增加了协议的容错性。

这是符合现阶段网络技术的一种方案,现阶段带宽已经不是网络传输的瓶颈,往返时间才是,所以新的网络传输协议可以适当增加数据冗余,减少重传操作。 当然这种情况只适用于丢失一个包的情况下,如果丢失了多个包,就只能进行重传了。

QPACK 压缩

QPACK 压缩是 HTTP/3 中使用的字段压缩格式,可以使 HTTP/2 中使用的 HPACK 压缩格式与 QUIC 协议兼容,两种压缩格式通过使用不同的机制来满足传输层协议的要求。

那么 HTTP/3 中为什么不能继续使用 HPACK 压缩格式呢

因为 HPACK 格式是为 TCP 协议创建的格式,这种格式工作的前提就是默认数据字节流按照顺序达到,如果 HTTP/3 中继续使用 HPACK 压缩格式, 就会导致额外的 队列头部请求阻塞 (head of line blocking) 问题,因为 HPACK 依赖于对已经到达字段的引用。但是,HTTP/3 中的 QUIC 的传输层协议为 UDP, 数据字节不会按照顺序到达,因此 HPACK 格式中的动态表中可能会包含还未达到的数据的引用,于是就会阻塞直到被引用的数据到达。

为了解决这个问题,QPACK 引入了两种单向流类型: 编码器流和解码器流,除了传递 HTTP/3 消息的双向字节流之外,客户端和服务端可以选择性地打开这两个单向编码流, 将具体的指令传输给对方。因为流是单向的,所以发送方只需要发送数据即可,无需等到接收方的响应。

最终,虽然增加额外的单向字节流会带来一定的性能开销,但是却可以彻底解决 队列头部请求阻塞 (head of line blocking) 问题,而且 QPACK 格式规范为客户端和服务器实现提供了很高的自由度, 可以由实现方来决定具体的问题重要程度和方案倾向性: 缓解并解决队列头部请求阻塞还是实现更高级别的压缩效果。

QUIC 相比 TCP 的优势

  • QUIC 在用户空间实现而非内核,这样可以快速部署最新版本或自定义版本,TCP 在内核中实现,基本不太可能随时部署新版本
  • QUIC 具有更高级别的加密 (例如大部分 QUIC Header 都是加密的),TCP 本身不做数据加密,需要依赖于 TLS
  • QUIC 建立在传输层协议栈上,意味着可以随时切换传输协议,有着非常高的灵活性 (例如可以将底层的 UDP 切换到 TCP)

QUIC 存在的限制

  • 性能提升很大程度上取决于 QUIC 方案的实施,这包括操作系统发行版、协议栈实现版本、QUIC 的实现版本等因素
  • 使用 HTTP/3 之前需要进行 HTTP 版本协商,浏览器默认不支持 HTTP/3, 通常需要基于建立在 TCP 上的 HTTP/1 或 HTTP/2 来发送协商请求
  • 增加网络管理复杂度,由于 QUIC 会加密大部分数据,因此排查网络错误、优化网络性能和安全性、设置报警规则等工作都变得更加困难,由于这些原因,很多防火墙还未支持 QUIC
  • 许多网络攻击都是利用 UDP 发起的,据统计大概有 3% - 5% 的网络会直接过滤 UDP 请求 (DNS 等网络基础协议除外)
  • 基于 UDP 传输的 HTTP/3,NAT 设备仅知道连接建立时间,但是无法得知连接断开时间,如果 NAT 设备的会话时间小于用户的会话时间,用户会话会中断,如果 NAT 设备的会话时间大于用户的会话时间,白白浪费一个 NAT 设备端口号 (一个可行方案是让 QUIC 周期性地发送心跳给 NAT 设备,维护会话)

为什么 HTTP/3 不直接基于 IP 层实现?

QUIC 协议没有端口号的概念,如果直接使用 IP 网络层传输,意味着每台主机都必须拥有一个公网 IP, 这在目前大多数互联网用户都使用 NAT 的情况下不太现实。


是否支持 HTTP/3

网站检测

可以通过 在线工具 进行检测。

HTTP/3 在线检测工具

浏览器支持情况

可以通过 浏览器在线工具 进行检测,下面是笔者的 Chrome 支持情况。

HTTP/3 在线检测工具


小结

本文介绍了从 HTTP/1 到 HTTP/3 中间四次比较大的协议升级变更,着重分析了每次升级前后的性能差异及相关特性变更 (安全方面的变更未分析,感兴趣的读者可以自行阅读相关 RFC)。 作为开发者,平时可能不会去关注这些网络底层细节,但是深入理解整个HTTP 协议的升级变迁过程,可以帮助我们理解这背后的工程挑战以及解决思路,这才是最有价值的部分。

终极用户体验模式

浏览器作为操作系统,站点作为应用软件,这算不算是从 B/S 架构又回到了 C/S 架构?

Reference

扩展阅读

转载申请

本作品采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,商业转载请联系作者获得授权。