页面和资源的变化
让我们从一些好消息开始:如果你已经在使用 HTTP/2,那么在切换到 HTTP/3 时,你可能不需要对页面或资源做任何更改!这是因为,正如我们在第一部分和第二部分中所解释的,HTTP/3 实际上更像是在 QUIC 上运行的 HTTP/2,两个版本的高级特性基本保持一致。因此,为 HTTP/2 进行的任何更改或优化在 HTTP/3 中仍然适用,反之亦然。
然而,如果你仍然在使用 HTTP/1.1,或者你忘记了过渡到 HTTP/2,或者你从未为 HTTP/2 进行过任何调整,那么你可能想知道这些变化是什么以及为什么需要它们。然而,即使在今天,要找到详细介绍细致最佳实践的好文章也相当困难。这是因为,正如我在第一部分的介绍中所述,早期的 HTTP/2 内容对它在实践中的表现过于乐观,而且有些内容实际上存在重大错误和不良建议。遗憾的是,这些错误信息今天仍然存在。这是我写这个关于 HTTP/3 系列的主要动机之一,希望能够防止类似情况再次发生。
目前我可以推荐的关于 HTTP/2 的最全面细致的资源是 Barry Pollard 的《HTTP/2 in Action》「www.manning.com/books/http2… HTTP/3 的关系:
1、单一连接
HTTP/2 与 HTTP/1.1 最大的区别在于从 6 个并行的 TCP 连接切换到了一个底层的单一 TCP 连接。我们在第二部分中讨论过,单一连接之所以能够与多个连接一样快,是因为拥塞控制可能导致更多或更早的数据包丢失,(从而消除了它们聚合起来更快启动的优势)。HTTP/3 延续了这种方法,但 “仅仅” 是从一个 TCP 切换到一个 QUIC 连接。这种差异本身并没有太大作用(主要减少了服务器端的开销),但它导致了以下大部分观点。
2、服务器分片和连接合并
单一连接的设置转换实际上相当困难,因为许多页面被分片到不同的主机名甚至服务器上(例如 img1.example.com 和 img2.example.com)。这是因为浏览器每个单独的主机名仅打开了六个连接,因此多路会允许更多的连接!如果不更改这个 HTTP/1.1 的设置,HTTP/2 仍然会打开多个连接,降低了其他功能的效果,比如优先级(参见下文)。
因此,最初的建议是取消服务器分片,并在单个服务器上尽可能合并资源。HTTP/2 甚至提供了一项功能,使从 HTTP/1.1 设置过渡变得更容易,称为连接合并。简而言之,如果两个主机名 (通过 DNS) 解析到相同的服务器 IP,并使用类似的 TLS 证书,则浏览器可以跨两个主机名重用单个连接。
在实践中,正确设置连接合并可能会很棘手,例如由于涉及一些 CORS 的微妙的安全问题。即使你正确设置了它,你仍然可能最终得到两个单独的连接。不过,这并不总是坏事。首先,由于优先级和多路复用(见下文)实现不佳,单一连接可能比使用两个或更多连接慢。其次,使用过多的连接可能导致由于竞争的拥塞控制器而导致早期的数据包丢失。然而,使用少量(但仍然超过一个)连接,可以在高速网络上平衡拥塞的增长和更好的性能。因此,我认为即使在 HTTP/2 中,保持一些分片仍然是个好主意(比如说,两到四个连接)。实际上,我认为大多数现代的 HTTP/2 设置之所以表现得如此好,是因为它们在关键路径上仍然有一些额外的连接或第三方加载。
3、资源捆绑和内联
在 HTTP/1.1 中,每个连接只能有一个活动资源,导致 HTTP 级的头部阻塞(HoL)。由于连接数被限制在寥寥无几的 6 到 30 个,资源捆绑(将较小的子资源组合成一个较大的资源)是长期的最佳实践。我们今天仍然在 Webpack 等打包工具中看到这种情况。类似地,资源通常会内联到其他资源中(例如,在 HTML 中内联关键 CSS)。
然而,使用 HTTP/2,单一连接多路复用资源,因此您可以有更多的未完成文件请求(换句话说,单个请求不再占用宝贵的连接之一)。最初的解释是,“我们不再需要为 HTTP/2 捆绑或内联我们的资源”。这种方法被宣传为更适合细粒度缓存,因为每个子资源可以单独缓存,如果其中一个更改,不需要重新下载完整的捆绑包。这是正确的,但仅在相对有限的范围内。
例如,您可能会降低压缩效率,因为它在处理更多数据时效果更好。此外,每个额外的请求或文件都具有固有的开销,因为它需要被浏览器和服务器处理。与几个大文件相比,对于数百个小文件,这些成本可能会积累起来。在我们自己的早期测试中,我发现在约 40 个文件时,回报急剧减小。尽管这些数字现在可能略高一些,但文件请求在 HTTP/2 中仍然不像最初预测的那样廉价。最后,不内联资源会增加额外的延迟,因为需要请求文件。这与优先级和服务器推问题结合在一起(见下文),意味着即使今天,内联一些关键的 CSS 仍然更好。也许将来的资源捆绑提案会帮助解决这个问题,但现在还没有。
当然,所有这些对于 HTTP/3 也是成立的。尽管我读过一些人声称,许多小文件在 QUIC 上可能更好,因为更多同时活动的独立流意味着从 HoL 阻塞移除更多的利润(如我们在第二部分「中讨论的)。我认为这可能有一些道理,但也正如我们在第二部分中也看到的,这是一个高度复杂的问题,有很多可变因素。我认为这些好处不会超过其他讨论的成本,但需要进行更多的研究。(一个令人吃惊的想法是,使每个文件的大小正好适合单个 QUIC 数据包,完全绕过 HoL 阻塞。如果有创业公司实现了这一点的资源捆绑器,我将接受版税。;))
4、优先级
为了能够在单一连接上下载多个文件,你需要以某种方式进行多路复用。正如第二部分讨论的那样,在 HTTP/2 中,这种多路复用是通过其优先级系统引导的。这也是为什么尽可能在同一连接上请求尽可能多的资源是重要的 — 以便能够在它们之间正确地设置优先级!然而,正如我们还看到的,这个系统非常复杂,导致它在实践中经常被错误使用和实施(见下图)。这反过来意味着一些关于 HTTP/2 的其他建议 — 比如减少捆绑,因为请求是廉价的,减少服务器分片,以充分利用单一连接(见上文) — 在实践中都表现不佳。
实施不当的 HTTP/2 堆栈可能会导致高优先级资源(底部的两个)落后于其他低优先级下载(所有其余资源)。
很遗憾,这是你作为一名普通的网络开发者不能做太多事情的事情,因为这主要是浏览器和服务器本身的问题。但是,你可以尝试通过不使用太多的单独文件(这将降低竞争优先级的机会)以及仍然使用(有限的)分片来缓解问题。另一种选择是使用各种影响优先级的技术,例如懒加载、JavaScript 的 async 和 defer,以及像 preload「 这样的资源提示。在内部,这些主要是通过更改资源的优先级,使它们能够更早或更晚地被发送。然而,这些机制可能(而且确实)存在错误。此外,不要指望简单地在一堆资源上添加 preload 就能使事情变得更快:如果一切突然成为高优先级,那么什么都不是高优先级!甚至使用类似 preload 的方法很容易通过使用类似 preload 的东西来延迟实际关键资源。
正如第二部分所解释的,HTTP/3 在根本上改变了这个优先级系统的内部结构。我们希望这意味着在实际部署中将会有更少的错误和问题,因此至少其中一些应该得到解决。然而,由于目前很少有 HTTP/3 服务器和客户端完全实现此系统,我们无法确定。尽管如此,优先级的基本概念不会改变。你仍然不能在没有真正了解内部发生了什么的情况下使用 preload 等技术,因为它可能仍然会错误地设置你的资源的优先级。
5、服务器推和首次请求
服务器推允许服务器在等待客户端请求之前发送响应数据。再次在理论上,这听起来很棒,它可以用来代替资源的内联(见上文)。然而,正如第二部分讨论的那样,推送由于拥塞控制、缓存、优先级和缓冲等问题而非常难以正确使用。总体来说,除非你真的知道你在做什么,最好不要将其用于常规网页加载,即使这样可能只是一种微优化。我仍然相信它可能在(REST)API 中有一席之地,其中你可以在已经预热的连接上推送在(JSON)响应中链接的子资源。这对 HTTP/2 和 HTTP/3 都是成立的。
总的来说,我觉得对于 TLS 会话恢复和 0-RTT 也可以提出类似的评论,无论是通过 TCP + TLS 还是通过 QUIC。正如第二部分讨论的那样,0-RTT 在很多方面类似于服务器推(通常使用),因为它试图加速页面加载的最初阶段。然而,这意味着它在那个时候能够实现的目标同样有限(在 QUIC 中更是如此,因为涉及到安全问题)。因此,微优化可能是你可能需要在低级别上微调事物才能真正从中受益的方式。想想我曾经非常兴奋地尝试将服务器推与 0-RTT 结合使用。
这一切意味着什么?
所有上述归结为一个简单的经验法则:应用大多数在网上找到的典型 HTTP/2 建议,但不要把它们推向极端。
以下是一些在大多数情况下都适用于 HTTP/2 和 HTTP/3 的具体观点:
1、在关键路径上通过一个到三个连接分片资源(除非你的用户主要在低带宽网络上),在需要的地方使用 preconnect 和 dns-prefetch。
2、逻辑上根据路径或功能,或根据变更频率捆绑子资源。每页五到十个 JavaScript 和五到十个 CSS 资源应该就够了。内联关键 CSS 仍然可以是一个好的优化。
3、谨慎使用复杂功能,比如 preload。
4、使用支持 HTTP/2 优先级的服务器。对于 HTTP/2,我推荐使用 H2O。Apache 和 NGINX 大致可以(尽管可能做得更好),而 Node.js 在 HTTP/2 中应该避免。对于 HTTP/3,目前情况不太清楚(见下文)。
5、确保在你的 HTTP/2 Web 服务器上启用 TLS 1.3。
正如你所看到的,虽然不是很简单,但为 HTTP/3(和 HTTP/2)优化页面并不是火箭科学。然而,更难的是正确设置 HTTP/3 服务器、客户端和工具。
服务器与网络
如您现在可能已经了解的那样,QUIC 和 HTTP/3 是相当复杂的协议。从头开始实现它们将涉及阅读(并理解!)分散在七个以上文档中的数百页内容。幸运的是,多家公司已经在开源 QUIC 和 HTTP/3 实现上工作了五年多,因此我们有几个成熟稳定的选择可供选择。
一些最重要和稳定的实现包括:
然而,许多这些实现主要关注 HTTP/3 和 QUIC 的处理;它们本身并不是真正的全功能网络服务器。对于典型的服务器(比如 NGINX,Apache,Node.js),由于多种原因,事情进展较慢。首先,其中少数开发人员最初涉足 HTTP/3,现在他们必须迎头赶上。许多服务器通过在内部使用上面列出的其中一个实现作为库来绕过这个问题,但即使是这种集成也很困难。
其次,许多服务器依赖于第三方的 TLS 库,比如 OpenSSL。这是因为 TLS 非常复杂且必须是安全的,因此最好重复使用现有的经过验证的工作。然而,尽管 QUIC 集成了 TLS 1.3,但它以与 TLS 和 TCP 交互的方式大不相同。这意味着 TLS 库必须提供 QUIC 特定的 API「,而它们的开发人员一直不愿意或进展缓慢。尤其是 OpenSSL,它推迟了对 QUIC 的支持,但许多服务器仍在使用它。这个问题变得如此严重,以至于 Akamai 决定启动一个名为 quictls 的 QUIC 专用的 OpenSSL 分支。虽然还有其他选择和解决方案,但对于许多服务器来说,TLS 1.3 对 QUIC 的支持仍然是一个障碍,而且预计将在一段时间内保持这样。
以下是您应该能够直接使用的一些全功能网络服务器,以及它们当前的 HTTP/3 支持:
- Apache 支持目前不清楚,没有宣布。可能还需要使用 OpenSSL。(请注意,有一个 Apache Traffic Server 的实现。)
- NGINX 这是一个定制实现,相对较新,仍然是高度实验性的。预计将在 2021 年底之前合并到 NGINX 的主线。此外,还有一个补丁可以在 NGINX 上运行 Cloudflare 的 quiche 库,这可能是目前更稳定的选择。
- Node.js 它在内部使用 ngtcp2 库。它受到 OpenSSL 进展的阻碍,尽管他们计划转向 QUIC-TLS 分支以尽快实现功能。
- IIS 支持目前不清楚,没有宣布。可能将在内部使用 MsQuic 库。
- Hypercorn 这集成了 aioquic,并提供实验支持。
- Caddy 这使用 quic-go,并得到全力支持。
- H2O 这使用起来很快,并且有全力支持。
- Litespeed 这使用 LSQUIC,并提供全力支持。
请注意一些重要的细微差别:
- 即使 “全面支持” 意味着 “目前的最佳状态”,但并不一定意味着 “已经准备好投入生产使用”。许多实现目前尚未完全支持连接迁移、零往返、服务器推送或 HTTP/3 优先级。
- 未列出的其他服务器,如 Tomcat,据我了解,目前尚未宣布任何消息。
- 在列出的网络服务器中,只有 Litespeed、Cloudflare 的 NGINX 补丁和 H2O 是由与 QUIC 和 HTTP/3 标准化密切相关的人制作的,因此这些可能最早能够最好地工作。
正如您所看到的,服务器领域目前还没有完全准备好,但肯定已经有一些选择可以设置 HTTP/3 服务器。然而,仅仅运行服务器只是第一步。配置它以及网络的其余部分更加困难。
网络配置
如第一部分所述,QUIC 运行在 UDP 协议之上,以便更容易部署。然而,这主要只是意味着大多数网络设备可以解析和理解 UDP。遗憾的是,这并不意味着 UDP 被普遍允许。因为 UDP 经常用于攻击,并且除了 DNS 之外,它对正常的日常工作几乎没有影响,所以许多(企业)网络和防火墙几乎完全阻止该协议。因此,可能需要明确允许 UDP 到 / 从您的 HTTP/3 服务器。QUIC 可以运行在任何 UDP 端口上,但期望端口 443(通常用于 HTTPS over TCP)最为常见。
然而,许多网络管理员可能不想简单地允许 UDP。相反,他们可能希望专门允许 UDP 上的 QUIC。问题在于,正如我们所见,QUIC 几乎完全是加密的。这包括 QUIC 级别的元数据,如数据包编号,以及例如指示连接关闭的信号。对于 TCP,防火墙主动跟踪所有这些元数据以检查预期的行为。(在数据承载数据包之前我们是否看到了完整的握手?数据包是否遵循预期模式?有多少个打开的连接?)正如我们在第一部分中看到的,这正是 TCP 实际上不再可进化的原因之一。然而,由于 QUIC 的加密,防火墙几乎无法执行这种连接级别的跟踪逻辑,而它们可以检查的少数位相对复杂。
因此,许多防火墙供应商目前建议在更新软件之前阻止 QUIC。即使在那之后,许多公司可能也不愿意允许它,因为防火墙对 QUIC 的支持始终会远远低于他们习惯的 TCP 功能。
连接迁移功能使这一切变得更加复杂。正如我们所见,这个功能允许连接从新的 IP 地址继续,而无需执行新的握手,使用连接 IDs(CIDs)。但是对于防火墙来说,这将看起来好像正在使用新的连接,而没有首先进行握手,这可能很可能是攻击者发送恶意流量。防火墙不能简单地使用 QUIC CIDs,因为它们随时间变化以保护用户的隐私!因此,需要服务器与防火墙沟通有关哪些 CIDs 是预期的,但这些事情尚不存在。
对于规模较大的设置,负载均衡器也存在类似的问题。这些机器将传入的连接分布到大量的后端服务器上。当然,一个连接的流量必须始终路由到相同的后端服务器(其他服务器将不知道该怎么做!)。对于 TCP,可以简单地基于 4-tuple 来完成,因为它永远不会改变。但是,由于 QUIC 连接迁移,这不再是一个选择。同样,服务器和负载均衡器将需要以某种方式达成一致,以确定选择哪些 CIDs 以允许确定性路由。然而,与防火墙配置不同,已经有一个提案来设置这一点(尽管这远未被广泛实施)。
最后,还有其他一些更高级别的安全考虑,主要涉及 0-RTT 和分布式拒绝服务(DDoS)攻击。正如第二部分中所讨论的,QUIC 已经包含了这些问题的相当多的缓解措施,但理想情况下,它们也将在网络上使用额外的防线。例如,代理服务器或边缘服务器可能会阻止某些 0-RTT 请求达到实际的后端,以防止重放攻击。或者,为了防止反射攻击或仅发送第一个握手数据包然后停止回复的 DDoS 攻击(在 TCP 中称为 SYN flood),QUIC 包含了重试功能。这允许服务器验证它是否是行为良好的客户端,而无需在此期间保留任何状态(相当于 TCP SYN cookies)。当然,这个重试过程最好发生在后端服务器之前的某个地方,例如在负载均衡器。然而,这需要额外的配置和通信来设置。
这些只是网络和系统管理员在处理 QUIC 和 HTTP/3 时可能遇到的最突出的问题。还有更多问题,其中一些我已经谈到了。有两份与 QUIC RFCs 配套的文件,讨论了这些问题及其可能的(部分)缓解措施。
这一切意味着什么?
HTTP/3 和 QUIC 是依赖于许多内部机制的复杂协议。并非所有这些机制都已经准备好投入实际使用,尽管您已经有一些选项可以在后端部署新的协议。然而,最著名的服务器和底层库(例如 OpenSSL)可能需要几个月甚至几年的时间才能更新。
即便如此,即使在最显著的服务器和底层库(例如 OpenSSL)得到更新之后,正确配置服务器和其他网络中介,以便以安全和最佳方式使用这些协议,对于较大规模的设置仍将是非常复杂的。您将需要一个良好的开发和运维团队来正确进行这一过渡。
因此,在早期阶段,最好依赖于一个大型托管公司或 CDN 为您设置和配置协议。正如第二部分中所讨论的,这就是 QUIC 最有可能付出的地方,使用 CDN 是您可以进行的关键性能优化之一。我个人建议使用 Cloudflare 或 Fastly,因为他们在标准化过程中参与密切,将拥有最先进和经过精心调整的实现。
客户端和 QUIC 发现
迄今为止,我们已经考虑了新协议在服务器端和网络支持方面的问题。然而,客户端方面还有一些问题需要克服。
在进入这些问题之前,让我们从一些好消息开始:大多数流行的浏览器已经支持(实验性的)HTTP/3!具体而言,在撰写时,以下是支持的情况(也可以参见 caniuse.com):
浏览器支持 HTTP/3 的情况很成熟。
- Google Chrome(版本 91+):默认启用。
- Mozilla Firefox(版本 89+):默认启用。
- Microsoft Edge(版本 90+):默认启用(内部使用 Chromium)。
- Opera(版本 77+):默认启用(内部使用 Chromium)。
- Apple Safari(版本 14):在手动标志后启用。将在版本 15 中默认启用,目前处于技术预览阶段。
- 其他浏览器:据我所知,还没有任何信号(尽管其他内部使用 Chromium 的浏览器,例如 Brave,理论上也可以开始启用它)。
请注意一些细微之处:
- 大多数浏览器都是逐渐推出的,即并非所有用户都会从一开始就默认启用 HTTP/3 支持。这是为了限制一个被忽视的错误可能影响许多用户或服务器部署超负荷的风险。因此,即使在最新的浏览器版本中,您也可能不会默认启用 HTTP/3,并且必须手动启用它的机会很小。
- 与服务器一样,HTTP/3 支持并不意味着所有功能都已经实现或正在使用。特别是 0-RTT、连接迁移、服务器推送、动态 QPACK 标头压缩和 HTTP/3 优先级可能仍然缺失、禁用、使用得很少或配置不良。
- 如果您想在浏览器之外(例如,在您的本机应用程序中)使用客户端 HTTP/3,那么您将不得不集成上面列出的库或使用 cURL。苹果将很快在其内置网络库上为 macOS 和 iOS 引入本机 HTTP/3 和 QUIC 支持,而 Microsoft 正在将 QUIC 添加到 Windows 内核和其.NET 环境中,但是(据我所知)尚未宣布其他系统(如 Android)是否提供类似的本机支持。
ALT-SVC
即使您已经设置了兼容 HTTP/3 的服务器并使用了更新的浏览器,您可能会惊讶地发现 HTTP/3 实际上并没有一致地使用。为了了解原因,让我们假设您是浏览器。您的用户已经要求您导航到 example.com(您以前从未访问过的网站),并且您已经使用 DNS 将其解析为 IP。您向该 IP 发送一个或多个 QUIC 握手数据包。现在有几种可能出现问题的情况:
- 服务器可能不支持 QUIC。
- 中间网络或防火墙可能完全阻止 QUIC 和 / 或 UDP。
- 握手数据包可能在传输过程中丢失。
然而,您怎么知道(哪个)这些问题发生了?在这三种情况下,您将永远不会收到握手数据包的回复。您唯一能做的就是等待,希望仍然可能会收到回复。然后,在等待一段时间(超时)之后,您可能会决定 HTTP/3 确实存在问题。在这一点上,您将尝试通过 TCP 连接到服务器,希望 HTTP/2 或 HTTP/1.1 能够工作。
正如您所见,这种方法可能会引入较大的延迟,特别是在初始的几年内,当许多服务器和网络尚未支持 QUIC 时。一个简单但幼稚的解决方案只是同时打开 QUIC 和 TCP 连接,然后使用先完成握手的连接。这种方法称为 “连接竞赛” 或 “快乐的眼球”。尽管这当然是可能的,但它确实有相当大的开销。即使输掉的连接几乎立即关闭,它仍然会在客户端和服务器上占用一些内存和 CPU 时间(特别是在使用 TLS 时)。除此之外,此方法还涉及 IPv4 与 IPv6 网络之间的其他问题以及先前讨论过的重放攻击(我在我的演讲「youtu.be/pq_xk_Pecu4…
因此,对于 QUIC 和 HTTP/3,浏览器宁愿选择谨慎行事,并且仅在它知道服务器支持时才尝试 QUIC。因此,第一次联系新服务器时,浏览器将仅使用 TCP 连接上的 HTTP/2 或 HTTP/1.1。然后,服务器可以通过在通过 HTTP/2 或 HTTP/1.1 发送的响应上设置特殊的 HTTP 头来告知浏览器它还支持 HTTP/3。这个头称为 Alt-Svc,即 “替代服务”。Alt-Svc 可用于让浏览器知道某个服务还可以通过另一个服务器(IP 和 / 或端口)访问,但它还允许指示替代协议。可以在下面的图 1 中看到这一点。
图 1:Facebook 包含 Alt-Svc 头,通知浏览器它也可以通过 UDP 端口 443 上的 HTTP/3 访问(有效期为 3600 秒)。目前,协议名称仍为 h3-29 或 h3-27(HTTP/3 的第 29 和 27 个草案版本),但最终将变为 h3(一些服务器,如 google.com,如今已经使用 h3 了)。
在接收到指示支持 HTTP/3 的有效 Alt-Svc 头后,浏览器将缓存此信息并尝试从那时开始建立 QUIC 连接。一些客户端会尽早执行此操作(即使在初始页面加载期间也会执行 — 见下文),而其他客户端将等到现有的 TCP 连接关闭。这意味着浏览器只会在通过 HTTP/2 或 HTTP/1.1 下载了至少一些资源之后才使用 HTTP/3。即便如此,事情还不尽如人意。浏览器现在知道服务器支持 HTTP/3,但这并不意味着中间网络不会阻止它。因此,在实践中仍然需要进行连接竞赛。因此,如果网络以某种方式延迟 QUIC 握手,您仍然可能以 HTTP/2 为最终协议。此外,如果 QUIC 连接连续几次建立失败,一些浏览器将在拒绝列表上将 Alt-Svc 缓存项放置一段时间,暂时不尝试 HTTP/3。因此,如果事情出现问题,手动清除浏览器的缓存可能是有帮助的,因为这也应该清空 Alt-Svc 绑定。最后,已经显示 Alt-Svc 可能带来一些严重的安全风险。因此,一些浏览器对其施加了额外的限制,例如可以使用的端口(在 Chrome 中,您的 HTTP/2 和 HTTP/3 服务器需要是在 1024 以下的端口上,否则 Alt-Svc 将被忽略或者两者都在 1024 或以上的端口上,否则 Alt-Svc 将被忽略)。所有这些逻辑在浏览器之间变化并且发展迅猛,这意味着获得一致的 HTTP/3 连接可能是困难的,这也使测试新的设置变得具有挑战性。
目前正在进行一项改进这两步 Alt-Svc 流程的工作。这个想法是使用名为 SVCB 和 HTTPS 的新 DNS 记录,它们将包含类似于 Alt-Svc 中的信息。因此,客户端可以在 DNS 解析步骤中发现服务器支持 HTTP/3,这意味着它可以在第一次页面加载时尝试 QUIC,而无需先经过 HTTP/2 或 HTTP/1.1。有关此项工作以及 Alt-Svc 的更多信息,请参见去年的 Web 年鉴关于 HTTP/2 的章节。
正如您所见,Alt-Svc 和 HTTP/3 发现过程为您已经具有挑战性的 QUIC 服务器部署添加了一层复杂性,因为:
- 您将始终需要在 HTTP/2 和 / 或 HTTP/1.1 服务器旁边部署您的 HTTP/3 服务器。
- 您将需要配置您的 HTTP/2 和 HTTP/1.1 服务器以在其响应上设置正确的 Alt-Svc 头。
尽管在生产级别的设置中应该是可以管理的(例如,因为单个 Apache 或 NGINX 实例可能会同时支持所有三个 HTTP 版本),但在(本地)测试设置中可能会更加麻烦(我已经看到自己忘记添加 Alt-Svc 头或搞砸它们的可能性)。当前浏览器错误日志和 DevTools 指示器的缺乏使得弄清楚设置为什么不起作用可能很困难。
其他问题
如果这还不够,另一个问题将使本地测试变得更加困难:Chrome 使您很难使用自签名的 TLS 证书进行 QUIC。这是因为非官方的 TLS 证书通常由公司用于解密员工的 TLS 流量(以便他们可以,例如,让他们的防火墙扫描加密流量内部)。但是,如果公司开始在 QUIC 上执行此操作,我们将再次拥有自己对协议的假设的定制中间件实现。这可能导致它们未来破坏协议支持,这正是我们一开始对 QUIC 进行如此广泛的加密所试图防止的!因此,Chrome 对此采取了非常明确的立场:如果您不使用官方 TLS 证书(由 Chrome 信任的证书颁发机构或根证书签名,例如 Let's Encrypt),那么您不能使用 QUIC。遗憾的是,这也包括经常用于本地测试设置的自签名证书。
仍然可以通过一些奇怪的命令行标志绕过这一点(因为通常的 --ignore-certificate-errors 对 QUIC 尚未起作用),方法是使用每个开发人员证书(尽管设置这可能很繁琐)或在您的开发 PC 上设置真实证书(但这对于大型团队来说很少是一个选项,因为您将不得不与每个开发人员共享证书的私钥)。最后,虽然您可以安装自定义根证书,但在启动 Chrome 时传递的标志还需要包含 --origin-to-force-quic-on 和 --ignore-certificate-errors-spki-list (见下文)。幸运的是,目前只有 Chrome 如此严格,希望它的开发人员会随着时间的推移放宽他们的态度。
如果您在浏览器内部遇到 QUIC 设置问题,最好首先使用诸如 cURL「 之类的工具对其进行验证。cURL 具有出色的 HTTP/3 支持(您甚至可以在两个不同的底层库之间进行选择),并且还使得观察 Alt-Svc 缓存逻辑变得更加容易。
这一切意味着什么?
除了在服务器端设置 HTTP/3 和 QUIC 时面临的挑战之外,还存在让浏览器一致使用新协议的困难。这是由于涉及 Alt-Svc HTTP 头的两步发现过程以及 HTTP/2 连接不能简单地 “升级” 到 HTTP/3,因为后者使用 UDP。
然而,即使服务器支持 HTTP/3,客户端(和网站所有者!)也需要处理中间网络可能阻止 UDP 和 / 或 QUIC 流量的事实。因此,HTTP/3 永远不会完全取代 HTTP/2。在实践中,保持调优良好的 HTTP/2 设置将继续对于首次访问者和非许可网络上的访问者都是必要的。幸运的是,正如我们讨论过的那样,HTTP/2 和 HTTP/3 之间不应该有太多页面级变化,因此这不应该是一个主要的头痛。
然而,可能会成为问题的是测试和验证您是否使用了正确的配置以及协议是否按预期使用。这在生产中是真实的,但在本地设置中尤为如此。因此,我预计大多数人将继续运行 HTTP/2(甚至是 HTTP/1.1)开发服务器,仅在稍后的部署阶段切换到 HTTP/3。然而,即便如此,使用当前一代工具验证协议性能仍然不容易。
工具与测试
与许多主要服务器一样,最受欢迎的网页性能测试工具的制造商并未从一开始就跟上 HTTP/3 的步伐。因此,截至 2021 年 7 月,虽然它们在一定程度上支持 HTTP/3,但很少有工具专门支持这一新协议。
谷歌 LIGHTHOUSE 工具
首先,有谷歌 LIGHTHOUSE 工具套件。虽然这是一款在一般情况下非常出色的网页性能工具,但我始终觉得它在协议性能方面有些欠缺。这主要是因为在浏览器中它以一种相对不切实际的方式模拟慢网络(与 Chrome 的 DevTools 处理方式相同)。虽然这种方法足够好用且通常足够了解慢网络对页面影响的一般情况,但测试低级协议差异并不现实。因为浏览器无法直接访问 TCP 堆栈,它仍然通过正常网络下载页面,然后人为延迟数据到达必要的浏览器逻辑。这意味着,例如,LIGHTHOUSE 仅模拟延迟和带宽,而不模拟数据包丢失(正如我们所见,这是 HTTP/3 可能与 HTTP/2 有所不同的主要点)。或者,LIGHTHOUSE 使用高度先进的模型来估算真实网络影响,因为例如谷歌 Chrome 在检测到慢网络时会调整页面加载的几个方面的复杂逻辑。据我所测,这个模型目前尚未调整以处理 IETF QUIC 或 HTTP/3。因此,如果你今天仅出于比较 HTTP/2 和 HTTP/3 性能的目的使用 LIGHTHOUSE,那么你可能会得到错误或过于简化的结果,这可能会导致你错误地得出关于 HTTP/3 在实践中对你的网站能做什么的结论。值得一提的是,理论上,这在未来可以得到极大改进,因为浏览器确实可以完全访问 QUIC 堆栈,因此 LIGHTHOUSE 可以逐步添加更高级的模拟(包括数据包丢失!)以用于 HTTP/3。然而,目前,虽然 LIGHTHOUSE 理论上可以加载使用 HTTP/3 的页面,我建议不要使用它。
WebPageTest
其次,有 WebPageTest。这个令人惊叹的项目允许您在全球真实设备上通过真实网络加载页面,并允许您在其上添加基于数据包的网络仿真,包括数据包丢失等方面!因此,从概念上讲,WebPageTest 是一个理想的工具,可用于比较 HTTP/2 和 HTTP/3 性能。然而,尽管它确实已经能够加载新协议的页面,但 HTTP/3 尚未被完全整合到工具或可视化中。例如,目前没有简单的方法来强制通过 QUIC 加载页面,轻松查看 Alt-Svc 是如何实际使用的,甚至查看 QUIC 握手详细信息。在某些情况下,甚至确定响应到底使用了 HTTP/3 还是 HTTP/2 可能会有一定的挑战。尽管如此,在四月份,我成功使用 WebPageTest 对 facebook.com 运行了一些测试,看到了 HTTP/3 的实际效果,现在我将详细介绍。
首先,我对 facebook.com 运行了一个默认测试,启用了 “重复查看” 选项。如上所述,我期望第一次页面加载将使用 HTTP/2,其中将包括 Alt-Svc 响应头。因此,重复查看应该从一开始就使用 HTTP/3。在 Firefox 89 版本中,这基本上是发生的。然而,当查看单独的响应时,我们看到即使在第一次页面加载期间,Firefox 也会在第 20 个资源之后切换到使用 HTTP/3 而不是 HTTP/2!正如图 2 所示,这发生在第 20 个资源之后。这意味着 Firefox 一旦看到 Alt-Svc 头就建立一个新的 QUIC 连接,一旦连接成功后就切换到它。如果您滚动到连接视图,它似乎还显示 Firefox 甚至打开了两个 QUIC 连接:一个用于具有凭据的 CORS 请求,另一个用于没有 CORS 请求。这是预期的,因为正如我们上面讨论的,即使对于 HTTP/2 和 HTTP/3,由于安全问题,浏览器也会打开多个连接。然而,由于 WebPageTest 在此视图中未提供更多详细信息,因此很难在没有手动查看数据的情况下确认。在查看重复视图(第二次访问)时,它首先直接使用 HTTP/3 进行第一个请求,这是预期的。
图 2:Firefox 在第一次页面加载中途切换到使用 HTTP/3。
接下来,对于 Chrome,我们看到第一次页面加载的行为类似,尽管在这里 Chrome 已经在第 10 个资源上切换,比 FireFox 早多了。这里稍微不太清楚,它是尽快切换还是仅在需要新连接时(例如,对于具有不同凭据的请求)才切换,因为与 Firefox 不同,连接视图似乎也没有显示多个 QUIC 连接。对于重复视图,我们看到一些更奇怪的事情。出乎意料的是,Chrome 在那里也开始使用 HTTP/2,仅在几个请求之后才切换到 HTTP/3!我进行了一些其他页面的测试,以确认这确实是一致的行为。这可能是由于几种原因:可能是 Chrome 当前的策略,可能是 Chrome “竞速” 了 TCP 和 QUIC 连接,而 TCP 首先获胜,或者可能是第一视图的 Alt-Svc 缓存由于某种原因未被使用。在这一点上,很遗憾,目前没有简单的方法确定问题真正是什么(以及是否甚至可以修复)。
我在这里注意到的另一件有趣的事情是连接合并行为。如上所述,无论是 HTTP/2 还是 HTTP/3,都可以在转到其他主机名时重用连接,以防止由于主机名分片而带来的缺点。然而,正如图 3 所示,WebPageTest 报告说,在 facebook.com 和 fbcdn.net 上,HTTP/3 的连接合并是使用的,但在 HTTP/2 上却不是(因为 Chrome 为第二个域名打开了一个辅助连接)。我怀疑这是 WebPageTest 中的一个错误,因为 facebook.com 和 fbcnd.net 解析为不同的 IP,因此实际上不能被合并。
该图还显示了当前 WebPageTest 可视化中缺少一些关键的 QUIC 握手信息。
图 3:Chrome 似乎在 HTTP/3 上合并 Facebook 连接,但在 HTTP/2 上不合并。
注意:正如我们所看到的,有时候确实很难启动 “真正的” HTTP/3。幸运的是,特别是对于 Chrome,我们有可以使用命令行参数测试 QUIC 和 HTTP/3 的其他选项。
在 WebPageTest 的 “Chromium” 选项卡底部,我使用了以下命令行选项:
--enable-quic --quic-version=h3-29 --origin-to-force-quic-on=www.facebook.com:443,static.xx.fbcdn.net:443
从这个测试的结果可以看出,这确实从一开始就强制使用 QUIC 连接,即使在第一视图中也是如此,从而绕过了 Alt-Svc 的过程。有趣的是,您会注意到我不得不传递两个主机名给 --origin-to-force-quic-on。在我没有的版本中,Chrome 当然首先在重复视图中向 fbcnd.net 域打开了一个 HTTP/2 连接。因此,您需要手动指示所有 QUIC 来源,以使其正常工作!
即使从这几个例子中,我们也可以看到浏览器实际上在实践中如何使用 HTTP/3。似乎它们甚至会在初始页面加载期间切换到新协议,放弃 HTTP/2,无论是尽快还是在需要新连接时。因此,不仅很难获得完整的 HTTP/3 加载,而且在支持两者的设置上获得纯 HTTP/2 加载也很困难!由于 WebPageTest 尚未显示太多关于 HTTP/3 或 QUIC 的元数据,了解到底发生了什么可能会很具有挑战性,您也不能完全信任工具和可视化的表面价值。
因此,如果您使用 WebPageTest,您需要仔细检查结果,以确保实际上使用了哪些协议。因此,我认为现在测试 HTTP/3 的性能真的太早了(特别是与 HTTP/2 进行比较),这也是因为并非所有服务器和客户端都已经实现了所有协议功能。由于 WebPageTest 尚未有简单的方法显示是否使用了高级功能,例如 0-RTT,因此了解您实际测量的是什么可能会很棘手。对于 HTTP/3 的优先级功能,这尤其是真的,因为并非所有浏览器都正确实现它,而且许多服务器也不完全支持。因为优先级可以是驱动 Web 性能的一个重要方面,所以在确保至少这个功能正常工作(对于两个协议都是如此!)之前,将 HTTP/3 与 HTTP/2 进行比较可能是不公平的。这只是一个方面,尽管我的研究表明 QUIC 实现之间的差异有多么大。如果您自己进行任何此类比较(或者阅读了这方面的文章),请确保百分之百确定您正在检查的是什么。
最后,请注意,其他更高级别的工具(或数据集,如令人惊叹的 HTTP 档案)通常是基于 WebPageTest 或 LIGHTHOUSE(或使用类似的方法),因此我认为我在这里的大多数评论可能广泛适用于大多数 Web 性能工具。即使对于那些在未来几个月宣布支持 HTTP/3 的工具供应商,我也会有些怀疑,并且会验证他们是否真正正确地做到了这一点。对于一些工具来说,情况可能会更糟,例如,谷歌的 PageSpeed Insights 今年才获得 HTTP/2 支持,所以我不会等待 HTTP/3 很快到来。
使用 Wireshark、qlog 和 qvis
如上讨论所示,仅使用 Lighthouse 或 WebPageTest 来分析 HTTP/3 行为可能会有些棘手。幸运的是,还有其他更低层次的工具可用于帮助解决这个问题。首先,优秀的 Wireshark 工具对 QUIC 有高级支持,还可以实验性地解析 HTTP/3。这使您能够观察到实际传输的 QUIC 和 HTTP/3 数据包。然而,为了使其工作,您需要获取给定连接的 TLS 解密密钥,大多数实现(包括 Chrome 和 Firefox)都允许您通过使用 SSLKEYLOGFILE 环境变量来提取这些密钥。虽然这对于某些事情可能很有用,但要真正弄清楚发生了什么,特别是对于较长的连接,可能需要大量手动工作。您还需要对协议内部运作有相当高级的理解。
幸运的是,还有第二个选择,即 qlog 和 qvis。qlog 是专门为 QUIC 和 HTTP/3 设计的基于 JSON 的日志格式,得到了大多数 QUIC 实现的支持。qlog 不是查看传输的数据包,而是直接在客户端和服务器上捕获此信息,这使其能够包含一些附加信息(例如,拥塞控制细节)。通常情况下,您可以在使用 QLOGDIR 环境变量启动服务器和客户端时触发 qlog 输出。(请注意,在 Firefox 中,您需要设置 network.http.http3.enable_qlog 首选项。Apple 设备和 Safari 使用 QUIC_LOG_DIRECTORY。Chrome 尚未支持 qlog。)
然后,可以将这些 qlog 文件上传到 qvis 工具套件(qvis.quictools.info)。在那里,您将获得许多先进的交互式可视化,使解释 QUIC 和 HTTP/3 流量变得更容易。qvis 还支持上传 Wireshark 数据包捕获(.pcap 文件),并具有对 Chrome 的 netlog 文件的实验性支持,因此您还可以分析 Chrome 的行为。有关 qlog 和 qvis 的详细教程超出了本文的范围,但可以在教程形式、论文形式甚至谈话形式找到更多详细信息。您也可以直接问我,因为我是 qlog 和 qvis 的主要实现者。;)
然而,我并不抱幻想大多数读者会使用 Wireshark 或 qvis,因为这些都是相当底层的工具。尽管如此,由于目前我们几乎没有其他选择,我强烈建议在没有使用这类工具的情况下不要广泛测试 HTTP/3 性能,以确保您真正了解发生在传输中的情况,以及您所看到的是否真的可以由协议内部而不是其他因素解释。
这一切意味着什么?
正如我们所见,建立并使用基于 QUIC 的 HTTP/3 可能是一项复杂的工作,许多事情可能会出错。遗憾的是,目前没有一个好的工具或可视化界面以适当的抽象级别显示必要的细节。这使得大多数开发人员很难评估 HTTP/3 对其网站可能带来的潜在好处,甚至验证他们的设置是否按预期工作。
仅依赖高级度量标准是非常危险的,因为这些可能受到许多因素的影响(例如不切实际的网络模拟,客户端或服务器上功能的缺失,仅部分使用 HTTP/3 等)。即使一切都运行得更好,正如我们在第二部分中看到的那样,HTTP/2 和 HTTP/3 之间的差异在大多数情况下可能相对较小,这使得在没有有针对性的 HTTP/3 支持的情况下,从高级工具中获得必要信息变得更加困难。
因此,我建议将 HTTP/2 与 HTTP/3 性能测量放置一段时间,而集中精力确保我们的服务器端设置按预期工作。对于这一点,最简单的方法是结合使用 WebPageTest 和 Google Chrome 的命令行参数,如果有潜在问题,则回退到 curl —— 目前这是我找到的最一致的设置。
结论与收获
亲爱的读者,如果你阅读了整个三部分系列并来到这里,我向你致敬!即使你只读了一些部分,我也感谢你对这些新颖而令人兴奋的协议的关注。现在,我将总结本系列的主要收获,为未来几个月和年份提供一些建议,并最后为你提供一些额外的资源,以便你想深入了解。
总结
首先,在第一部分中,我们讨论了 HTTP/3 主要是由于新的基础 QUIC 传输协议的出现。QUIC 是 TCP 的精神继承者,并集成了所有其最佳实践以及 TLS 1.3。这主要是因为 TCP 由于其无处不在的部署和在中间盒中的集成而变得过于僵化,难以发展演进。QUIC 使用 UDP 和几乎完全的加密意味着我们(希望如此)只需要在未来更新端点以添加新功能,这应该更容易。然而,QUIC 还增加了一些有趣的新功能。首先,QUIC 的组合传输和加密握手比 TCP + TLS 更快,并且可以充分利用 0-RTT 功能。其次,QUIC 知道它正在携带多个独立的字节流,可以更智能地处理丢失和延迟,缓解头部阻塞问题。第三,QUIC 连接可以通过使用连接 ID 为每个数据包打标签来保活用户切换到不同网络(称为连接迁移)。最后,QUIC 的灵活的数据包结构(采用帧)使其更有效,同时未来也更灵活和可扩展。总的来说,很明显,QUIC 是下一代传输协议,将在未来的许多年中被使用和扩展。
其次,在第二部分中,我们对这些新功能,特别是它们的性能影响,进行了一些批判性的审视。
- 首先,我们看到 QUIC 使用 UDP 并不会使其变得更快(也不慢),因为 QUIC 使用与 TCP 非常相似的拥塞控制机制,以防止过载网络。
- 其次,更快的握手和 0-RTT 是更微妙的优化,因为它们只比优化过的 TCP + TLS 堆栈快一个往返,而 QUIC 的真正 0-RTT 受到一系列安全问题的影响,这可能限制其有用性。
- 第三,连接迁移实际上只在一些特定情况下需要,而且仍然意味着重置发送速率,因为拥塞控制不知道新网络可以处理多少数据。
- 第四,QUIC 头部阻塞的有效性严重取决于流数据如何多路复用和优先级设置。从丢包中恢复的最佳方法似乎对网页加载性能的一般使用情况有害,反之亦然,尽管还需要更多研究。
- 第五,QUIC 发送数据包可能比 TCP + TLS 慢得多,因为 UDP API 不够成熟,并且 QUIC 会单独加密每个数据包,尽管这在未来可以在很大程度上缓解。
- 第六,HTTP/3 本身并没有真正为性能带来任何重大新功能,而主要是重新设计和简化了已知 HTTP/2 功能的内部。
- 最后,QUIC 允许的一些最令人兴奋的与性能相关的功能(多路径,不可靠数据,WebTransport,前向纠错,等)不是核心 QUIC 和 HTTP/3 标准的一部分,而是提出的扩展,需要一些时间才能使用。
总的来说,这意味着对于高速网络上的用户,QUIC 可能不会显著提高性能,但对于那些在慢速和不太稳定的网络上的用户来说,它可能非常重要。
最后,在这第三部分中,我们看到了如何实际使用和部署 QUIC 和 HTTP/3。
- 首先,我们看到大多数从 HTTP/2 中学到的最佳实践都应该延续到 HTTP/3。无需更改捆绑或内联策略,也无需整合或分片服务器群。服务器推送仍然不是最好的功能使用,preload 预加载同样可能是一个强大的风险。
- 其次,我们讨论了在现成的 Web 服务器软件包提供完整的 HTTP/3 支持之前可能需要一些时间(部分是由于 TLS 库支持问题),尽管对于早期采用者来说,有很多开源选项可用,并且一些主要 CDNs 提供了成熟的支持。
- 第三,大多数主流浏览器显然都支持(基本的)HTTP/3,甚至默认启用。然而,它们在实际使用 HTTP/3 及其新功能方面存在重大差异,因此理解它们的行为可能是具有挑战性的。
- 第四,我们讨论了由于流行工具(如 Lighthouse 和 WebPageTest)中缺乏明确的 HTTP/3 支持,这一点变得更加严重,这使得目前很难比较 HTTP/3 在性能上与 HTTP/2 和 HTTP/1.1。
总的来说,HTTP/3 和 QUIC 可能还没有完全准备好投入实战,但它们很快将准备好。
建议
从上面的总结来看,似乎我在强烈反对使用 QUIC 或 HTTP/3。然而,与我想要表达的观点正好相反。
首先,正如第二部分末尾所讨论的,即使你的 “普通” 用户可能不会遇到主要性能增益(取决于你的目标市场),但你的一部分受众很可能会看到令人印象深刻的改进。0-RTT 可能只会节省一个往返,但对于某些用户来说,这可能意味着几百毫秒。连接迁移可能不能持续一直快速下载,但它确实会帮助那些试图在高速列车上获取 PDF 的人。电缆上的数据包丢失可能是突发的,但无线链接可能更多受益于 QUIC 的移除头部阻塞功能。此外,这些用户通常是你的产品性能最差的用户,因此受到其影响最大。如果你想知道为什么这很重要,请阅读 Chris Zacharias 的著名网络性能轶事。
其次,QUIC 和 HTTP/3 将随着时间的推移变得越来越好和更快。第 1 版本的重点是完成基本协议,并将更先进的性能功能留给以后。因此,我认为现在开始投资于这些协议是值得的,以确保在它们未来可用时,你可以充分利用它们和新功能。鉴于协议及其部署方面的复杂性,最好花点时间了解它们的怪癖。即使你现在不想亲自动手,一些主要的 CDN 提供商提供成熟的 “切换开关” HTTP/3 支持(特别是 Cloudflare 和 Fastly)。如果你使用 CDN(如果你关心性能,确实应该使用),我很难找到不试一试的理由。
因此,虽然我不会说尽快开始使用 QUIC 和 HTTP/3 是至关重要的,但我确实觉得已经有很多好处可以得到,而且它们将来只会增加。