AI工具人
提示词工程师

HTTP/3:性能改进(第 2 部分)

欢迎回到关于新 HTTP/3 协议的系列文章。在第 1 部分中,我们探讨了为什么需要 HTTP/3以及底层 QUIC 协议的主要特性。

在第二部分中,我们将重点探讨 QUIC 和 HTTP/3 为网页加载带来的性能改进,同时也会客观审视这些新特性在实践中的实际效果。

事实上,QUIC 和 HTTP/3 确实展现出显著的 Web 性能提升潜力,但这主要体现在慢速网络环境中。对于使用高速有线或移动网络的普通用户来说,新协议带来的优势可能并不明显。不过值得注意的是,即使在网络基础设施较好的地区,仍有 1% 到 10% 的用户(即第 99第 90 百分位的用户)可能会显著受益。这是因为 HTTP/3 和 QUIC 主要解决的是当今互联网上那些不常见但影响深远的问题。

本部分内容比第一部分更偏重技术性,但我们会将较为深奥的细节放在外部资源中,重点阐述这些技术变革对普通 Web 开发者的实际意义。

速度初探

讨论性能和"速度"是一个复杂的话题,因为网页加载速度受多种因素影响。由于我们主要讨论网络协议,我们将聚焦于两个最关键的网络因素:延迟和带宽。

延迟指的是数据包从起点(如客户端)到终点(如服务器)的传输时间。它受物理限制,如光速或信号在传输介质中的传播速度。简单来说,延迟主要取决于两点之间的实际物理距离。

理论上,单向延迟通常在10到200毫秒之间。但数据传输是双向的:数据包发出后还需要等待响应返回。这个来回的时间称为往返时间(RTT)

由于拥塞控制等机制(详见下文),加载单个文件往往需要多次往返。因此,即使是50毫秒这样较低的延迟也会累积成显著的等待时间。这就是为什么内容分发网络(CDN)如此重要:通过将服务器部署在离用户更近的位置,可以有效减少延迟。

带宽则可以简单理解为网络同时能够传输的数据包数量。它受多个因素影响,包括传输介质的物理特性(如无线电波频率)、网络用户数量,以及网络设备的处理能力。

人们常用水管来比喻网络传输:管道长度代表延迟,管径代表带宽。但在互联网中,实际情况是我们面对着一系列相连的管道,每段管道的粗细可能不同。较细的管道会形成瓶颈,限制整个路径的传输能力。因此,两点之间的实际带宽取决于路径中最慢的链路。

虽然不需要完全掌握这些概念就能理解本文后续内容,但对这些基本定义有所了解会很有帮助。想深入了解的读者可以参考Ilya Grigorik在《高性能浏览器网络》关于延迟和带宽的详细讨论

拥塞控制

传输协议的性能很大程度上取决于它能够多有效地利用网络的全部(物理)带宽,即每秒能发送和接收的数据包数量。这直接影响着页面资源的下载速度。虽然有人声称 QUIC 在这方面远胜于 TCP,但实际情况并非如此。

小知识:TCP 连接在启动时不会立即使用全部带宽发送数据,这是为了避免网络过载(或拥塞)。每个网络链路在物理上都有每秒可处理的数据量限制。当数据量超出限制时,网络只能丢弃多余的数据包。对于 TCP 这类可靠协议来说,恢复丢失的数据包只能通过重新传输,这需要额外的一次往返时间。在高延迟网络中(如 RTT 超过 50 毫秒),数据包丢失会显著降低性能。

另一个挑战是我们无法预知最大带宽。这取决于端到端连接中的瓶颈位置,但我们既无法预测也无法确定瓶颈在哪里。目前,互联网还没有一种机制能向端点报告链路容量。

而且,即便我们知道可用的物理带宽,也不能独占使用。网络通常同时服务多个活跃用户,每个用户都应该获得公平的带宽份额。

因此,连接无法预先确定可以安全或公平地使用多少带宽,且可用带宽会随着用户的加入、离开和使用而变化。为了应对这个问题,TCP 采用了拥塞控制机制,持续探测可用带宽的变化。

连接建立时,它先发送少量数据包(通常是 10 到 100 个,约14 到 140 KB数据),然后等待一个往返时间,直到收到接收方的确认。如果所有数据包都得到确认,说明网络能够承载当前发送速率,于是可以尝试发送更多数据(实际上,发送速率通常会在每次迭代中翻倍)。

通过这种方式,发送速率会持续增加,直到出现未被确认的数据包(表明发生了数据包丢失和网络拥塞)。这个初始阶段被称为"慢启动"。一旦检测到数据包丢失,TCP 会降低发送速率,然后(经过一段时间后)以较小的增量重新开始提速。此后每次发生数据包丢失都会重复这种减速后缓慢加速的过程。这样,TCP 就能不断调整以达到理想的、公平的带宽份额。图 1 展示了这个机制。

图 1. TCP 拥塞控制的简化示例,从 10 个数据包的发送速率开始(改编自hpbn.co。(大预览

https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/9d5e5ddd-7a90-48c6-84c5-dc5c335e2305/congestion-control.png

这是对拥塞控制的极其简化的解释。实际上还有许多其他重要因素,包括缓冲区膨胀拥塞导致的 RTT 波动,以及如何让多个并发连接获得公平的带宽份额。虽然目前存在多种拥塞控制算法,并且新算法仍在不断涌现,但没有一种算法能在所有场景下都表现最佳。

TCP 的拥塞控制虽然强大,但需要时间才能达到最佳发送速率,这取决于 RTT 和实际可用带宽。对于网页加载来说,这种慢启动方式会影响首次内容绘制等指标,因为初始几次往返只能传输有限的数据量(几十到几百 KB)。(这也是为什么有人建议将关键资源控制在 14 KB 以内。)

在高带宽和高延迟网络中,采用更激进的方法可能会带来更好的性能,特别是在能够容忍偶尔数据包丢失的情况下。这让我想到了人们对 QUIC 工作原理的一些误解

如第 1 部分所述,理论上 QUIC 对数据包丢失(及其相关的队头阻塞问题)的抵抗力更强,因为它能独立处理每个资源流上的数据包丢失。此外,QUIC 基于用户数据报协议(UDP),与 TCP 不同,UDP 本身没有拥塞控制机制,允许以任意速率发送数据且不会重传丢失的数据包。

这导致许多文章错误地认为 QUIC 不需要拥塞控制,可以通过 UDP 以更高速率发送数据(仅依靠消除队头阻塞来应对数据包丢失),并据此声称 QUIC 比 TCP 更快。

实际情况并非如此QUIC 实际采用了与 TCP 极为类似的带宽管理方式。它也是从低速率起步,循序渐进地提高发送速率,并通过确认机制来评估网络容量。这样设计有几个原因:QUIC 需要保证可靠性以支持 HTTP 等协议,必须与其他 QUIC 和 TCP 连接公平地共享带宽,而且它的队头阻塞消除机制实际上并不足以有效预防数据包丢失(这一点我们稍后会详细探讨)。

不过,这并不意味着 QUIC 在带宽管理方面就不能比 TCP 更智能。关键在于QUIC 的架构更灵活,更易于演进。正如前文所述,拥塞控制算法仍在不断发展,我们可能需要进行调整以充分利用 5G 等新技术

TCP 通常实现在操作系统内核中,这是一个安全性要求高、限制较多的环境,而且在大多数操作系统中并非开源。因此,对拥塞控制逻辑的调整通常只能由少数开发者完成,发展较为缓慢。

相比之下,当前大多数 QUIC 实现都在"用户空间"(即普通应用程序运行的环境)中完成,并且是开源的。这种设计明确旨在鼓励更多开发者参与实验(比如Facebook已经展示的成果)。

一个具体的例子是 QUIC 的延迟确认频率扩展提案。QUIC 默认每收到 2 个数据包就发送一次确认,而这个扩展允许每 10 个数据包才确认一次。这种改进在卫星网络和高带宽网络上带来了显著的性能提升,因为大幅降低了确认数据包的传输开销。相比之下,为 TCP 添加类似的扩展需要很长时间才能推广使用,而 QUIC 则能更快地部署新特性。

因此可以预见,QUIC 的灵活性将推动更多实验和更优秀的拥塞控制算法的诞生,这些进步最终也可能反哺 TCP 的发展。

小知识:QUIC Recovery RFC 9002 采用 NewReno 拥塞控制算法。虽然这是一个可靠的算法,但在现代应用中已不太常见。为什么 QUIC RFC 会做出这个选择呢?主要有两个原因:在 QUIC 开发初期,NewReno 是唯一标准化的拥塞控制算法,而 BBR 和 CUBIC 等更先进的算法尚未完成标准化。其次,NewReno 结构简单,更适合用来说明如何根据 QUIC 与 TCP 的差异来调整算法。因此,RFC 9002 应该被视为"如何为 QUIC 适配拥塞控制算法"的指南,而非"QUIC 必须使用的标准算法"。实际上,大多数生产环境中的 QUIC 实现都选择了定制版的 Cubic 和 BBR 算法。值得注意的是,拥塞控制算法可以在 TCP 和 QUIC 之间通用,而 QUIC 带来的创新也可能反过来促进 TCP 协议栈的发展。

小知识:除了拥塞控制,还有一个容易混淆的概念叫做流量控制。在 TCP 中,这两种机制都使用"TCP 窗口"这个术语,但实际指的是不同的窗口:拥塞窗口和接收窗口。由于流量控制对网页加载性能的影响较小,本文不作详细讨论。感兴趣的读者可以自行查阅相关资料。

这意味着什么? #

QUIC 仍然需要遵守物理定律,也要与互联网上的其他用户和谐共处。这意味着它不可能让您的网站资源下载速度神奇地超越 TCP。不过,QUIC 的灵活架构让我们能更容易地尝试新的拥塞控制算法,这将有望在未来同时促进 TCP 和 QUIC 的进步。

0-RTT 连接设置 #

第二个性能改进与连接建立时的往返次数有关:即在发送实际的 HTTP 数据(例如页面资源)之前需要多少次往返通信。尽管有人声称 QUIC 比 TCP + TLS 快两到三次往返,但实际上仅快一次往返。

小知识:如第 1 部分所述,建立 HTTP 连接前需要完成握手过程。这包括 TCP 握手,或者 TCP 和 TLS 的双重握手。这些握手用于交换必要的初始参数,主要是加密所需的信息。如图 2 所示,每次握手都需要至少一次往返(TCP + TLS 1.3,(b)),某些情况下需要两次往返(TLS 1.2 及更早版本,(a))。这种设计存在效率问题:在发送第一个 HTTP 请求前,我们必须等待至少两次握手完成。这意味着要经过至少三次往返才能收到第一个 HTTP 响应(如红色箭头所示)。在网络连接较慢时,这可能导致 100 到 200 毫秒的延迟。

图 2:TCP + TLS 与 QUIC 连接设置。(大预览

https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/9f4c69d1-5ab6-4ca2-ad1e-ec68d99dc9ab/connection-setup-1.png

你可能想知道:为什么不能将 TCP 和 TLS 握手合并为一次往返?虽然这在技术上可行(QUIC 就采用了这种方式),但 TCP 最初设计时考虑到要支持可选的 TLS。这导致 TCP 无法在握手阶段传输非 TCP 数据。尽管后来的 TCP Fast Open 扩展尝试解决这个问题,但正如第 1 部分所述,实践表明这个功能难以在实际环境中大规模部署

QUIC 在设计之初就整合了 TLS,将传输和加密握手合并为单一机制。这使得 QUIC 握手仅需一次往返即可完成,比 TCP + TLS 1.3 减少一次往返(参见图 2c)。

关于 QUIC 比 TCP 快两到三个往返的说法容易引起误解。这种比较实际上只针对最差情况(TCP + TLS 1.2,(a)),却忽略了现代 TCP + TLS 1.3 也"仅"需要两次往返((b))。虽然节省一次往返确实是项改进,但优势并不显著。在快速网络环境下(RTT 低于 50 毫秒时),这种优势几乎难以察觉。然而,在慢速网络和远程连接场景中,这种改进效果会更为明显。

为什么我们必须等待握手完成后才能发送 HTTP 请求呢?如果能在第一次往返时就发送请求,岂不是更快?问题在于安全性:这样做会导致第一个请求以明文形式传输,任何人都可能截获内容。因此,我们需要先完成加密握手,才能安全地发送首个 HTTP 请求。但是,这真的是唯一的方式吗?

这里有一个巧妙的解决方案。用户通常会在短时间内多次访问同一网页,我们可以利用首次加密连接为后续连接做准备。在第一次连接时,客户端和服务器会安全地交换新的加密参数,这些参数可直接用于加密后续连接,无需再等待完整的 TLS 握手。这种优化技术被称为"会话恢复"

这项技术带来了重要优化:我们可以在 QUIC/TLS 握手过程中安全地发送第一个 HTTP 请求,从而节省一次往返时间。对于 TLS 1.3,这实际上消除了 TLS 握手的等待时间。这种被称为 0-RTT 的方法可以显著提升速度(但 HTTP 响应数据仍需一次往返才能到达)。

值得注意的是,会话恢复和 0-RTT 并非 QUIC 独有功能。这些本质上是TLS 的功能,在 TLS 1.2 就已存在,并在TLS 1.3中得到进一步完善。

因此,如图 3 所示,TCP(包括 HTTP/2 和 HTTP/1.1)同样能获得这些性能优势。即使启用 0-RTT,QUIC 也仅比优化后的 TCP + TLS 1.3 快一个往返。有些人声称 QUIC 快三个往返,这是因为他们错误地将图 2(a)与图 3(f)进行了比较。

图 3:TCP + TLS 与 QUIC 0-RTT 连接设置。(大预览

https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/ddbd604f-cec4-4e61-9172-707eda88bc99/connection-setup-2.png

然而,出于安全考虑,QUIC 即使使用 0-RTT 也无法完全发挥这种往返时间节省的优势。要理解这一点,我们需要先了解 TCP 握手的一个重要目的:在传输任何高层数据之前,让客户端验证服务器是否确实在指定的 IP 地址上可用。

更重要的是,这让服务器能够在发送数据前验证客户端的身份和位置是否真实。如果您还记得我们在第 1 部分中介绍的 4 元组连接定义,就会知道客户端主要是通过 IP 地址来识别的。这就带来了一个严重问题:IP 地址可以被伪造

设想这样一个场景:攻击者通过 HTTP over QUIC 0-RTT 请求一个大文件,但伪造了 IP 地址,使请求看似来自受害者的计算机。如图 4 所示,由于这是 QUIC 服务器收到的第一个数据包,服务器无法判断 IP 是否被伪造。

图 4:攻击者可以在发送 QUIC 0-RTT 请求时伪造 IP 地址,从而对受害者发起放大攻击。(大预览

https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/64b9318e-3f18-4852-b911-409033d8645b/amplification.png

如果服务器开始将大文件发送到被伪造的 IP 地址,可能会导致受害者的网络带宽过载,尤其是当攻击者同时发起多个此类虚假请求时。即使受害者会丢弃这些意外的 QUIC 响应,其网络仍然不得不处理这些数据包。

这种攻击被称为反射攻击或放大攻击,是实施分布式拒绝服务(DDoS)攻击的常用手段。值得注意的是,TCP + TLS 的 0-RTT 不会出现这个问题,因为它必须先完成 TCP 握手,才能进行 TLS 握手和发送 0-RTT 请求。

因此,QUIC 在处理 0-RTT 请求时必须格外谨慎,它需要在验证客户端身份之前严格限制响应数据量。QUIC 协议规定,响应数据量不得超过客户端发送数据量的三倍

这个"放大倍数"限制为 3 是性能与安全性的平衡之选,这一限制相当保守,因为某些攻击可能达到51,000 倍以上的放大效果。考虑到客户端通常仅发送一到两个数据包,QUIC 服务器的 0-RTT 响应实际上被限制在了 4 到 6 KB(包括 QUIC 和 TLS 的开销),这是一个相当保守的限制。

此外,为了防范"重放攻击"等其他安全威胁,系统对可执行的 HTTP 请求类型进行了限制。例如,Cloudflare 的 0-RTT 仅支持不含查询参数的 HTTP GET 请求,这显著限制了 0-RTT 的实际应用场景。

幸运的是,QUIC 提供了一些应对方案。服务器可以验证 0-RTT 请求是否来自之前建立过有效连接的 IP 地址。然而,这种方法仅在客户端保持在同一网络时有效(这也限制了 QUIC 的连接迁移功能)。而且即使验证成功,QUIC 的响应速度依然受限于我们前面讨论的拥塞控制器慢启动机制。最终,除了节省一次往返时间外,实际性能提升并不显著。

小知识:需要注意的是,QUIC 的三倍放大限制也适用于图 2c 中的常规非 0-RTT 握手过程。当服务器的 TLS 证书超过 4-6 KB 限制时,证书必须分成多个部分发送。第二部分要等到第二次往返才能发送(也就是在收到前几个数据包的确认后,确认客户端 IP 地址是真实的)。在这种情况下,QUIC 握手可能需要两次往返,与 TCP + TLS 所需时间一样长!这就是为什么证书压缩等技术对 QUIC 特别重要。

小知识:一些高级配置可以缓解这些问题,让 0-RTT 更实用。比如,服务器可以记住客户端上次连接时的可用带宽,这样可以减少重连时拥塞控制慢启动的影响。学术界正在研究这个问题,甚至提议为 QUIC 添加相关扩展。一些公司已在 TCP 中实现了类似功能。另一个方案是让客户端发送更多数据包(例如发送 7 个带填充的数据包),这样即使在连接迁移后,三倍限制也能支持 12-14 KB 的响应大小。我在论文中详细讨论过这个方案。当然,如果 QUIC 服务器认为安全风险可控,或者不太担心潜在的安全问题(毕竟没有协议警察来管),也可以直接提高三倍的限制。

这意味着什么? #

QUIC 的 0-RTT 快速连接设置实际上只是一项小幅优化,而不是革命性突破。与最新的 TCP + TLS 1.3 相比,它只能节省一次往返时间,而且由于安全限制,首次往返能传输的数据量十分有限。

这项功能主要在两种情况下发挥作用:一是延迟特别高的网络环境(如延迟超过 200 毫秒的卫星网络),二是传输小规模数据的场景。最典型的应用包括高度缓存的网站,以及通过 API 和其他协议(如DNS-over-QUIC)频繁获取小型更新的单页应用。Google 在 0-RTT 测试中的出色表现,正是因为他们在高度优化的搜索页面上进行测试,这些页面的响应数据极小。

在其他场景下,性能提升仅有几十毫秒。如果网站已经使用了 CDN(这对重视性能的网站来说是必需的),实际提升会更加微不足道。

连接迁移 #

QUIC 的第三个性能特性通过保持连接状态来优化网络切换场景。尽管这项功能确实有效,但网络切换的情况并不常见,而且切换后仍然需要重新调整传输速率。

如第 1 部分所述,QUIC 的连接 ID(CID)允许在切换网络时保持连接。比如,当用户下载大文件时从 Wi-Fi 切换到 4G 网络,TCP 连接会断开并需要重新建立,而 QUIC 可以无缝继续传输。

首先,我们需要考虑连接迁移在实际中发生的频率。许多人可能认为,当我们在建筑物内切换 Wi-Fi 接入点或在路上切换蜂窝基站时会触发连接迁移,但事实并非如此。在这些场景中(如果系统配置正确),设备会保持相同的 IP 地址,因为基站间的切换是在较低的协议层完成的。实际上,连接迁移仅在完全不同的网络之间切换时才会发生,这种情况并不常见。

其次,连接迁移的价值主要体现在大文件下载、实时视频会议和流媒体等场景中。对于普通网页浏览来说,即使在网络切换过程中,最多也只需要重新请求少量未完成的资源。

由于网络切换过程中通常存在重叠期,视频应用可以在新旧网络上同时建立连接,在旧网络完全断开前完成同步。这种方式下,用户虽然能感知到切换过程,但视频流不会完全中断。

第三,新网络的可用带宽往往低于旧网络。因此,即使连接在理论上保持不变,QUIC 服务器也无法维持原有的高速传输。为了避免网络拥塞,它必须重置(或至少降低)发送速率,并重新进入拥塞控制器的慢启动阶段

由于这种初始传输速率较低,即使使用 QUIC,视频流等应用的用户仍可能遇到画质下降或故障。因此,连接迁移的主要作用是避免连接状态丢失和减轻服务器负担,而非提升性能表现。

小知识:与前面讨论的 0-RTT 一样,我们可以使用一些高级技术来优化连接迁移。例如,通过记录特定网络上的历史带宽数据,我们可以在新的迁移中更快地恢复到理想的传输速度。更先进的方案是同时使用多个网络,而不是简单地在网络间切换。这种称为多路径的技术,我们将在后文详细介绍。

到目前为止,我们主要讨论了主动连接迁移(即用户在不同网络间移动)。但实际上还存在被动连接迁移,这种情况下网络参数会自动发生变化。网络地址转换(NAT)重新绑定是一个典型例子。虽然NAT的详细讨论超出了本文范围,但简而言之,它会导致连接的端口号可能随时变化,而且不会提前通知。在大多数路由器中,这种情况在UDP连接上比TCP连接上更常见。

在这种情况下,QUIC的CID会保持不变,且大多数实现会认为用户仍在同一物理网络上,所以不会重置拥塞窗口或其他参数。这种情况通常出现在连接长期空闲时,因此QUIC专门设计了预防机制,包括PING超时指示器

如我们在第1部分所述,出于安全考虑,QUIC不只使用单个CID。在执行主动迁移时,它会更改CID。实际情况更为复杂,因为客户端和服务器各自维护独立的CID列表(在QUIC RFC中称为源和目标CID),如图5所示。

图5:QUIC使用单独的客户端和服务器CID。(大预览

https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/6a0b9339-6976-458d-afc2-4a0cb97a7291/4-migration-src-dst-cid.png

这种设计让每个端点可以独立决定CID格式和内容,这对高级路由和负载均衡功能来说非常关键。在连接迁移时,负载均衡器不能仅依靠4元组来识别连接并路由到正确的后端服务器。如果所有QUIC连接都采用随机CID,会给负载均衡器带来巨大的内存压力,因为它必须维护CID和后端服务器的对应关系。更糟的是,当CID在连接迁移时更新为新的随机值时,这种方案就会失效。

为了解决这个问题,负载均衡器后面的QUIC服务器需要使用可预测的CID格式,这样负载均衡器即使在连接迁移后也能根据CID找到对应的后端服务器。IETF的提议文档提供了多种实现方案。这要求服务器能够自主选择CID——如果让连接发起方(在QUIC中永远是客户端)来选择CID,就无法实现这个目标。这就解释了为什么QUIC协议中客户端和服务器需要使用不同的CID。

这一切意味着什么? #

连接迁移主要是一个针对特定场景的功能。Google的初步测试表明,在大多数用例中性能提升并不显著。目前,许多QUIC实现还未支持此功能,已实现的也往往仅限于移动端应用,而不包括桌面版。有观点认为这个功能可能并非必需,因为在多数情况下,使用0-RTT建立新连接就能达到相似的性能效果。

然而,这个功能的价值取决于具体使用场景和用户群体。如果您的网站或应用主要用于移动场景(比如Uber或Google Maps),相比于主要在办公环境下使用的情况,您能获得更多收益。同样,如果您的应用主要用于实时交互(如视频聊天、协作编辑或游戏),相比新闻网站,在网络切换时您能获得更显著的性能改善。

队头阻塞消除 #

第四个性能特性是消除队头(HoL)阻塞问题。这使得 QUIC 在高数据包丢失率的网络环境下表现出色。不过,虽然理论上这种改进很有意义,但在实际的网页加载场景中,性能提升可能并不显著。

为了深入理解这一点,我们首先需要了解两个关键概念:流优先级和多路复用。

流优先级 #

如第 1 部分所述,TCP 将所有数据视为单个文件的一部分,因此一个数据包的丢失就可能会导致多个资源传输延迟。而 QUIC 则不同,它能识别多个并发字节流,并独立处理每个流的丢失情况。这些流虽然不是真正并行传输的,但通过多路复用机制共享同一连接。多路复用可以采用多种不同方式实现。

以流 A、B 和 C 为例,一种方式是数据包序列ABCABCABCABCABCABCABCABC,即轮流处理不同的流(称为轮询方式)。与之相对的是AAAAAAAABBBBBBBBCCCCCCCC,即一个流完全传输完后再传输下一个(称为顺序方式)。在这两种极端方式之间,还存在多种变体(如AAAABBCAAAAABBC…AABBCCAABBCC…ABABABCCCC…等)。HTTP 层的流优先级功能会动态控制使用哪种多路复用方案(本文后续将详细讨论)。

多路复用方案的选择对网站加载性能有重大影响。这在Cloudflare提供的视频中可以清楚看到,不同浏览器采用的多路复用方式各不相同。这是一个复杂的主题,我不仅发表了多篇学术论文,还在会议上做过专题演讲Webpagetest的知名作者 Patrick Meenan 甚至专门录制了一个长达三小时的详细教程

不同的流复用方式会显著影响各个浏览器中的网站加载性能。

https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/3407bbc0-fb9b-4a1d-af44-d8cad0f0ec19/compare.gif

幸运的是,基础知识并不难掌握。您可能知道,某些资源会阻塞渲染,例如CSS文件和HTML head元素中的JavaScript。浏览器在加载这些文件时无法绘制页面或执行新的JavaScript。

CSS和JavaScript文件需要完整下载才能使用(尽管它们可以在下载过程中逐步解析和编译)。因此,这些资源应该优先加载。让我们看看当A、B和C都是渲染阻塞资源时的情况。图6:流复用方法如何影响(渲染阻塞)资源的完成时间。

https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/46ae54c1-2985-4c47-9ebe-18686bbfc4ce/multiplexing-render-blocking.png

使用循环多路复用器(图6顶行)会导致每个资源的完成时间延长,这是因为所有资源必须共享有限的带宽。考虑到这些资源需要完整下载才能使用,这种方式会产生明显的延迟。相比之下,按顺序进行多路复用(图6底行)能让A和B更快完成并供浏览器使用,同时对C的完成时间影响较小。

然而,顺序复用并不总是最佳选择。某些非渲染阻塞资源(如HTML和渐进式JPEG)可以增量处理和使用。这种情况下,循环复用或混合方式反而更合适。

不过,对于大多数网页资源来说,顺序复用效果最好。这就是为什么Google Chrome采用这种方式,而Internet Explorer使用效果较差的循环复用器。

数据包丢失恢复能力 #

既然我们已经了解到流并非总是同时活动,而且可以用不同方式进行多路复用,让我们来探讨数据包丢失时的情况。如第 1 部分所述,当 QUIC 中的某个流遇到数据包丢失时,其他活动流可以继续传输数据,这与 TCP 中所有流都必须停止的情况形成鲜明对比。

然而,正如我们刚才所见,即使在没有数据包丢失的情况下,同时运行多个活动流也会降低网页性能,因为这会延迟关键的渲染阻塞资源。我们更倾向于使用顺序多路复用,每次只激活一到两个流,但这种做法也会削弱 QUIC 消除队头阻塞的优势。

以一个例子来说明:假设发送方在特定时间窗口内可以传输12 个数据包(如图 7 所示)——这个限制来自于拥塞控制器。如果我们将这 12 个数据包全部用来传输高优先级的渲染阻塞流 A(例如main.js),那么在这个时间窗口内就只存在一个活动流。

如果其中一个数据包丢失,QUIC 依然会遇到完全的队头阻塞。这是因为当前只有流A在传输:所有数据都属于A,而没有BC的数据可以处理,因此必须等待丢失的数据包重传。这种情况实际上与 TCP 的表现相似。

图 7:数据包丢失的影响取决于使用的多路复用方式。(注意,这里假设每个流的数据量比之前的示意图更多。)

https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/05412377-b92f-4adc-9725-4a3b4b4d602a/hol-blocking-rr-sequential.png

这里存在一个权衡:顺序复用(AAAABBBBCCCC)通常能带来更好的网页性能,但会限制 QUIC 消除队头阻塞的优势。相反,循环复用(ABCABCABCABC)虽然能更有效地避免队头阻塞,却会降低网页性能。这就导致了两种优化策略相互抵消

这个问题比表面看起来更复杂。我们之前只考虑了单个数据包丢失的情况,但现实并非如此简单。互联网上的数据包丢失通常呈"突发性",这意味着常常会同时丢失多个数据包

网络过载是导致数据包丢失的主要原因之一,因为系统必须丢弃超出负载的数据包。这就解释了为什么拥塞控制器最初会采用较慢的发送速率。不过,它会逐步增加传输速度,直到出现数据包丢失

换句话说,这个旨在防止网络过载的机制实际上会引发网络过载(尽管是以可控的方式)。在大多数网络中,这种情况会在一段时间后出现,当发送速率增加到每次往返数百个数据包时。一旦这些数据包达到网络容量上限,就会导致多个数据包同时丢失,形成突发性丢包模式。

小知识:这是我们倾向使用 HTTP/2 单个 (TCP) 连接,而非 HTTP/1.1 的 6-30 个连接的主要原因之一。每个连接都会以相似方式提高发送速率,这导致 HTTP/1.1 虽然初期加速表现不错,但多个连接最终会相互干扰,引发大量数据包丢失和网络过载。Chromium 开发团队认为这种现象是互联网数据包丢失的主要来源。这也解释了为什么 BBR 后来成为主流拥塞控制算法——它通过监测 RTT 波动而非数据包丢失来评估可用带宽。

小知识:在无线网络中,数据包丢失情况往往较为轻微,通常只涉及单个数据包。这类丢失一般在较低协议层就能被检测到,并在本地设备间(如智能手机和 4G 基站)直接处理,无需通过客户端和服务器重传。因此,这种情况通常不会导致真正的端到端数据包丢失,而是表现为数据包延迟(即"抖动")的变化和到达顺序的改变。

因此,如果我们使用循环复用器(每个数据包轮流发送不同流的数据:ABCABCABCABCABCABCABCABC…)来最大化 HoL 阻塞移除的优势,即使只发生 4 个数据包的突发丢失,也会影响到所有 3 个流(如图 8 中间行所示)。这种情况下,QUIC 的 HoL 阻塞移除特性完全失去了优势,因为每个流都必须等待自己丢失的数据包重传

图 8:根据所使用的多路复用器和数据包丢失模式,受影响的流会更多或更少。

https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/93cb3e10-16dd-4647-af85-91cc723d35f6/hol-blocking-bursty.png

为了降低突发丢失影响多个流的风险,我们需要为每个流进行更长时间的连续传输。例如,使用模式AABBCCAABBCCAABBCCAABBCC…是一个初步改进,而采用AAAABBBBCCCCAAAABBBBCCCC…(如图8底行所示)效果更为显著。这进一步证实了连续传输方式的优势,即使这意味着需要减少并发活动流的数量。

预测QUIC的队头阻塞(HoL)移除效果比较复杂,这是因为它受多个因素影响:流的数量、突发丢失的规模和频率,以及流数据的具体使用方式等。然而,目前的研究结果表明,在网页加载场景中,这项特性收益有限,主要是因为我们通常倾向于使用较少的并发流。

如果您想深入了解这个主题或查看具体示例,欢迎阅读我的HTTP队头阻塞详解文章

小知识:和前面一样,现代技术提供了一些解决方案。现代拥塞控制器使用数据包调步技术,不会一次性发送100个数据包,而是将它们均匀分散在整个RTT周期内。这种方法能降低网络过载风险,因此QUIC恢复RFC也强烈建议采用。此外,一些拥塞控制算法(如BBR)采用了更智能的方式:通过监测RTT波动(网络过载会导致RTT上升)来预判风险,并在数据包丢失前就主动降低发送速率。尽管这些技术确实降低了数据包丢失的整体概率,但仍然无法完全避免突发性丢包问题。

这一切意味着什么?

理论上,QUIC 消除队头阻塞的特性让它(和 HTTP/3)在网络丢包时具有更好的表现。但实际效果取决于多个因素。由于网页加载通常采用连续的多路复用方式,再加上数据包丢失具有随机性,这项功能主要影响到最慢的 1% 用户群。这仍是一个活跃的研究领域,最终效果还需要时间验证。

在某些特定场景下可能会带来更显著的改进,特别是在非典型的首次页面加载场景中——例如资源不会阻塞渲染、可以渐进式处理、流之间完全独立,或同时传输的数据量较小的情况。

典型例子包括访问已缓存的页面时,或在单页应用的后台数据获取和 API 调用中。Facebook 的实践证明,在其原生应用中使用 HTTP/3 加载数据时,消除队头阻塞确实带来了显著的性能提升。

UDP 和 TLS 性能

QUIC 和 HTTP/3 的第五个性能方面是数据包的创建和传输效率。由于 QUIC 采用 UDP 和高强度加密,其性能目前低于 TCP(虽然差距正在缩小)。

首先,我们此前讨论过,选择 UDP 主要是看中其灵活性和易部署性,而非性能优势。实际上,直到最近,QUIC 数据包的传输速度都明显慢于 TCP 数据包。这种差异主要源于两种协议的实现位置和方式不同(见下图 9)。

图 9:TCP 和 QUIC 的实现差异。

https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/cffc7945-bd57-459f-b6d3-ed06f51b30ad/kernel-user-space.png

TCP 和 UDP 通常直接在操作系统的快速内核中实现。TLS 和 QUIC 主要在较慢的用户空间中实现(虽然这对 QUIC 并非必需,但这样做提供了更大的灵活性)。这导致 QUIC 在性能上天然落后于 TCP。

此外,当用户空间软件(如浏览器和 Web 服务器)发送数据时,必须将数据传递给操作系统内核,再由内核通过 TCP 或 UDP 发送到网络上。这个数据传递过程需要通过内核 API(系统调用)完成,每次调用都会产生开销。相比之下,TCP 的这些开销要远低于 UDP。

这种差异主要源于历史原因:TCP 的使用率一直远高于 UDP。随着时间推移,TCP 的实现和内核 API 获得了大量优化,大大降低了数据包收发的开销。许多网络接口控制器(NIC)甚至为 TCP 提供了内置的硬件卸载功能。相比之下,UDP 因使用场景有限,未能获得类似的优化。不过,情况在过去五年发生了变化,大多数操作系统都**为 UDP增加了优化选项**。

其次,QUIC 面临的另一个性能瓶颈是必须对每个数据包单独加密。与 TCP 上的 TLS 相比这种方式更慢,因为 TLS 可以批量处理数据包加密(每次最多处理 16 KB 或 11 个数据包)。这是 QUIC 的一个权衡决策,因为批量加密可能会引入新的队头阻塞问题

与第一个问题不同,UDP(及 QUIC)的性能可以通过添加 API 来提升,但加密方面的劣势是 QUIC 相对于 TCP + TLS 的固有问题。不过,通过使用优化的加密库和支持批量加密 QUIC 数据包头的创新方法,这个问题在实践中可以得到缓解。

谷歌最早的 QUIC 版本比 TCP + TLS 慢两倍,但现在性能已显著提升。例如,最近的测试表明,微软高度优化的 QUIC 堆栈可达到 7.85 Gbps,而同系统上的 TCP + TLS 为 11.85 Gbps(QUIC 速度达到 TCP + TLS 的 66%)。

最新的 Windows 更新大幅提升了 UDP 性能,使该系统的 UDP 吞吐量达到 19.5 Gbps。Google 优化后的 QUIC 堆栈现在仅比 TCP + TLS 慢约 20%。值得注意的是,Fastly 在较旧系统上通过优化实现了与 TCP 相当的性能(约 450 Mbps),这证明 QUIC 在特定场景下完全可以与 TCP 抗衡。

即便 QUIC 的速度比 TCP + TLS 慢一倍,实际影响也并不显著。这是因为在服务器端,QUIC 和 TCP + TLS 的处理并非最耗资源的环节,还有 HTTP、缓存、代理等其他处理逻辑。因此,**运行 QUIC 实际上不需要双倍的服务器资源(但由于缺乏大公司的公开数据,目前还不清楚它在实际数据中心中**产生多大影响)。

此外,QUIC 的实现仍有巨大的优化潜力。随着时间推移,部分 QUIC 实现将向操作系统内核迁移(类似 TCP),或采用内核绕过技术(如MsQuic已经实现)。未来我们也有望看到QUIC 专用硬件的出现。

然而,某些场景中 TCP + TLS 仍是最佳选择。例如,Netflix 表示近期不太可能采用 QUIC,原因是他们已经在定制的 FreeBSD 环境中投入大量资源优化 TCP + TLS 视频传输。

同样,Facebook 也指出,QUIC 可能主要应用于终端用户和 CDN 边缘节点之间的通信,而不会用于数据中心之间或边缘节点与源服务器之间的通信,这是因为其开销较大。因此在未来几年内,高带宽场景很可能会继续选择 TCP + TLS。

小知识:网络堆栈优化是一个深奥且技术性的领域,本文仅触及了皮毛。如果你想深入了解,特别是对 GRO/GSO、SO_TXTIME、内核旁路以及 sendmmsg() 和 recvmmsg() 等技术感兴趣,建议阅读 Cloudflare 和 Fastly 关于 QUIC 优化的系列文章,以及微软的详细代码解析和思科的深度技术演讲。另外,有一场谷歌工程师的精彩演讲,详细介绍了他们是如何逐步优化 QUIC 实现的。

这一切意味着什么? #

由于 QUIC 对 UDP 和 TLS 协议的特殊使用方式,其速度确实比 TCP + TLS 慢。不过,持续的优化和改进已经逐步缩小了这个差距。对于普通的网页浏览,这种性能差异几乎无法察觉。但对于管理大规模服务器集群的运营者来说,这种性能差异可能会成为一个值得关注的问题。

HTTP/3 功能

我们目前主要探讨了 QUIC 对比 TCP 的性能优势。那么 HTTP/3 与 HTTP/2 相比如何呢?正如第 1 部分所述,HTTP/3 实质上是 HTTP/2-over-QUIC,因此没有引入重大新功能。这与 HTTP/1.1 升级到 HTTP/2 时的情况不同,当时新增了标头压缩、流优先级和服务器推送等多项重要功能。这些功能在 HTTP/3 中依然保留,但底层实现方式发生了显著变化。

这是由于 QUIC 处理 HoL 阻塞的独特方式。如前所述,当流 B 丢包时,流 A 和 C 可以继续传输而无需等待 B 重传,这与 TCP 有着本质区别。这意味着即使按 A、B、C 顺序发送 QUIC 数据包,浏览器可能会按 A、C、B 的顺序接收和处理它们。简而言之,与 TCP 相比,QUIC 不再要求不同流之间保持严格的顺序

这给 HTTP/2 带来了一个重大挑战。HTTP/2 的许多功能都依赖于 TCP 的严格顺序传输,并通过在数据块中插入特殊控制消息来实现。而在 QUIC 中,这些控制消息可能会以任意顺序到达和处理,这甚至可能导致功能的执行结果与预期相反!虽然本文不涉及技术细节,但这篇文章的前半部分可以帮助您理解其中的复杂性。

因此,HTTP/3 需要重新设计这些功能的内部机制。以HTTP 标头压缩为例,该功能用于减少重复HTTP标头(如cookie和用户代理字符串)所占用的空间。HTTP/2 采用HPACK实现压缩,而 HTTP/3 则使用了更复杂的QPACK。虽然这两个系统的目标相同(标头压缩),但它们的实现方式完全不同。如果想了解更多技术细节,Litespeed 博客提供了详细的讨论和图表说明。

流复用的优先级功能也面临类似挑战,我们前面已经讨论过。HTTP/2 采用复杂的"依赖树"机制来管理所有页面资源及其关系(详见"HTTP 资源优先级终极指南")。但在 QUIC 上使用这种机制会出现问题,因为每个资源都通过独立的控制消息添加到树中,可能导致树结构错误。

这种复杂的方法带来了大量实现错误和效率问题以及服务器性能下降。因此,HTTP/3 的优先级系统采用了更简单的设计。虽然这种简化设计使某些高级场景(如在单一连接上代理多个客户端流量)变得困难或无法实现,但它仍然为网页加载优化提供了充分的选项。

两种方法虽然都提供了相同的基本功能(流复用控制),但 HTTP/3 的简化设计更易于正确实现。

最后来看服务器推送功能。这项功能允许服务器在客户端未明确请求的情况下发送 HTTP 响应,理论上可以显著提升性能。但实践证明,这个功能难以正确使用,而且实现不一致。因此,它很可能会从 Google Chrome 中移除

尽管如此,它仍然定义为 HTTP/3 的标准功能(尽管实际支持的实现很少)。它的内部机制虽然没有像前两个功能那样经历重大改变,但也进行了调整以适应 QUIC 的非确定性顺序。可惜的是,这些调整并未能解决它长期存在的根本问题。

这一切意味着什么?

总的来说,HTTP/3 的核心优势源自底层的 QUIC 协议,而不是 HTTP/3 本身。尽管 HTTP/3 在内部实现上与 HTTP/2 有很大区别,但从用户角度来看,其高级性能特性和使用方式基本保持一致。

未来值得关注的发展

在本系列中,我多次强调 QUIC(以及 HTTP/3)的核心优势在于其快速演进和灵活性。研究人员已开始探索协议的新扩展和应用,这是意料之中的。以下是未来可能出现的主要发展方向:

  • 前向纠错技术旨在提高 QUIC 对数据包丢失的恢复能力。通过发送经过编码和压缩的数据冗余副本,当数据包丢失但冗余数据到达时,就无需重新传输。虽然这项功能最初是 Google QUIC 的一部分,但由于性能影响尚未得到验证,未被纳入标准化的 QUIC 版本 1。目前,研究人员正在积极实验这项技术,您可以通过PQUIC-FEC 下载实验参与他们的研究。
  • 多路径 QUIC技术探索了同时使用 Wi-Fi 和蜂窝网络的可能。正如我们此前讨论的连接迁移在网络切换时提供帮助,多路径技术通过同时使用两个网络来提供更多带宽和更高的连接稳定性。研究表明,尽管 Google 曾因其复杂性未将其纳入 QUIC 版本 1,但这项技术很可能会进入版本 2。值得注意的是,类似技术的实际应用往往需要近十年的发展时间。
  • **通过 QUICHTTP/3**传输不可靠数据。尽管 QUIC 是可靠协议,但由于它基于 UDP,我们可以添加不可靠数据传输功能。这在数据报扩展提案中有详细说明。虽然不适用于网页资源传输,但对游戏和实时视频流极为有用,既能获得 UDP 的优势,又能享受 QUIC 的加密和可选拥塞控制。
  • WebTransport是一项新技术,它补充了现有的WebSocketWebRTC。出于安全考虑,浏览器不直接暴露 TCP 或 UDP,而是提供 HTTP 级 API。WebTransport 允许以更底层方式使用 HTTP/3 和 QUIC,必要时可回退到 TCP 和 HTTP/2。它支持不可靠数据传输,特别适合浏览器游戏开发。常规 API 调用仍可使用自动选择 HTTP/3 的 Fetch。目前 WebTransport 仍在讨论阶段,Chromium 已有概念验证实现
  • DASH 和 HLS 视频流是点播视频(如 YouTube 和 Netflix)常用的协议。它们将视频分割为小块(2-10秒)并提供多种质量级别。浏览器实时估算最佳质量级别并请求相应文件。由于无法直接访问内核中的 TCP 堆栈,这些估算可能不准确或适应较慢。QUIC 在浏览器中实现,通过让流估算器访问底层协议信息可显著改善这一问题。研究人员还在探索混合可靠和不可靠数据传输,并取得了积极成果。
  • HTTP/3 以外的协议也在向 QUIC 迁移。由于 QUIC 是通用传输协议,许多基于 TCP 的应用层协议都可能采用它。目前正在开发的包括DNS-over-QUICSMB-over-QUICSSH-over-QUIC。这些协议的需求与 HTTP 和网页加载不同,QUIC 的性能改进可能在这些场景中发挥更大作用。

这一切意味着什么?

QUIC 版本 1只是一个开始。尽管 Google 早期试验的许多高级性能功能未被纳入此版本,但该协议旨在通过快速迭代来持续引入新的扩展和功能。这意味着随着时间发展,QUIC(和 HTTP/3)将在性能和灵活性方面越来越优于 TCP(和 HTTP/2)。

结论

在本系列的第二部分中,我们探讨了 HTTP/3 和 QUIC 的各种性能特性。研究表明,虽然这些特性在理论上很有前景,但在实际的网页加载场景中,普通用户可能难以感知明显的改进。

以 QUIC 使用 UDP 为例,这并不会自动带来更高的带宽或更快的资源下载速度。而备受推崇的 0-RTT 功能实际上只是一个小优化,最多能在一次往返中节省约 5 KB 的数据传输。

在遇到突发数据包丢失或加载阻塞渲染的资源时,消除 HoL 阻塞带来的改善并不显著。连接迁移的效果也高度依赖具体场景,而 HTTP/3 相比 HTTP/2 并未引入能显著提升速度的新功能。

看到这里,您可能会质疑采用 HTTP/3 和 QUIC 的必要性。但我的建议恰恰相反!虽然这些新协议可能不会给高速网络用户带来显著优势,但对于经常移动的用户和慢速网络用户而言,这些新特性可能带来重大改善。

即便在像比利时这样的西方国家,我们虽然普遍享有快速设备和高速移动网络,这些问题仍会影响 1% 到 10% 的用户群,具体取决于您的产品。设想一下:有人在火车上急需访问您网站上的关键信息,却要等待 45 秒才能加载完成。我就经历过这种情况,真希望那时已经部署了 QUIC。

在其他国家和地区,情况更为严峻。那里的普通用户可能比比利时最慢的 10% 用户还要慢,而最慢的 1% 甚至可能完全无法加载页面。在世界很多地方,网络性能已成为一个可访问性和包容性问题

这就是为什么我们不应该仅在自己的设备上测试页面(而应该使用Webpagetest等服务),也是为什么您绝对应该部署 QUIC 和 HTTP/3 的原因。特别是当您的用户经常处于移动状态或难以访问高速移动网络时,这些新协议可能带来显著改善,即使您在有线连接的 MacBook Pro 上感受不到明显差异。想了解更多细节,我强烈推荐阅读Fastly 关于这个问题的文章

如果您仍在犹豫,请考虑 QUIC 和 HTTP/3 在未来几年将持续发展并变得更快。现在积累协议经验将在未来获得回报,让您能更快享受新功能带来的好处。此外,QUIC 在后台强制执行安全和隐私最佳实践,这将惠及全球所有用户。

现在您被说服了吗?那就继续阅读本系列的第 3 部分,了解如何在实践中部署这些新协议。

赞(0) 打赏
未经允许不得转载:XINDOO » HTTP/3:性能改进(第 2 部分)

评论 抢沙发

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫

微信扫一扫