翻译、编辑:Alex
技术审校:刘连响
本文来自_Smashing Magazine_,原文链接:
https://www.smashingmagazine.com/2021/09/http3-practical-deployment-options-part3/
Robin讲HTTP/3 #006#
经过近五年的开发,新的HTTP/3协议终于接近尾声。接下来让我们详细了解一下在部署和测试HTTP/3时所遇到的挑战,以及如何或是否也应该更改你的网站和资源。
大家好,欢迎来到HTTP/3和QUIC协议系列的第三部分,也是最后一部分!在学习了前两部分内容之后,你已经相信开始使用新协议是一个不错的主意(你也应该如此!),接下来最后一部分的内容囊括了你需要知道的所有入门知识!
首先,我们将讨论需要对网页和资源进行哪些更改才能充分使用这些新协议(这一部分比较容易)。接着,我们将了解如何设置服务器和客户端(这一部分较难,除非你使用了CDN)。最后,我们将了解可以使用哪些工具来评估新协议所带来的性能影响(这一部分几乎无法实现,至少目前如此)。
网页和资源的更改
让我们从一些好消息开始:**如果你已经在使用HTTP/2,那么在向HTTP/3迁移时,你可能无需更改网页和资源!**我们在第一部分和第二部分曾解释过:这是因为HTTP/3更像是HTTP/2-over-QUIC,而且这两个版本的高层特性没有发生变化。因此,任何对HTTP/2的更改和优化也将同样适用于HTTP/3,反之亦然。
然而,如果你正在使用HTTP 1.1,或者你忘了向HTTP/2过渡,又或者你从未调整过HTTP/2,那么你也许想知道究竟需要什么样的更改以及为什么需要这些更改。即使在今天,你也很难找到详细描述这些具备细微差别的最佳实践的好文章。正如我在第一部分所介绍的那样,这是因为早期 HTTP/2 的大部分内容都过于乐观地认为它的实际效果会很好,但坦诚讲,其中一些包含了重大错误和糟糕的建议。遗憾的是,很多错误信息在今天依然存在。这也是我创作HTTP/3系列文章的主要原因:防止重蹈覆辙。
此刻,我要大家推荐Barry Pollard[1]的 HTTP/2 in Action [2]一书,它是最全面且最细致入微介绍HTTP/2的图书。不过,这是一个付费资源,我不想你在这里各种猜测,所以我下面列出了一些要点,以及它们与HTTP/3的关系。
1. 单一连接(Single Connection)
HTTP/1.1与HTTP/2之间最大的差异是:从6~30个并行TCP连接切换到单一的底层TCP连接。我们曾在第二部分讨论过,由于拥塞控制在多个连接的情况下会产生更多或者更早的丢包(这抵消了多个连接拥有较快开始的优势),所以单一连接依然可以和多个连接一样快。HTTP/3继续使用这种方法,但“只是”从一个TCP连接切换到了一个QUIC连接。这种差异本身并没有太大作用(它主要减少了服务器端的开销),但接下来的大部分内容都是由这种差异引起的。
2. 服务器sharding和连接合并(Server Sharding and Connection Coalescing)
实际中,切换到单一连接设置非常困难,因为许多页面被shard到不同的主机名甚至是服务器上(如img1.example.com 和 img2.example.com)。这是因为浏览器最多只能为每个主机名打开6个连接,所以多个主机名就要打开更多连接!如果不更改多个域名的设置,HTTP/2仍然会打开多个连接,从而降低了其他特性(比如优先级,见下文)的实际效果。
因此,最开始的建议是撤销服务器sharding并将资源尽量整合在一个服务器上。HTTP/2甚至提供了一个被称为连接合并[3]的特性。通过它,HTTP/2从HTTP/1设置的过渡变得更容易。简单来说,如果两个主机名解析为相同的服务器IP(使用DNS)并使用一个相似的TLS证书,那么浏览器甚至可以在两个主机名上重用单一连接。
由于涉及CORS的几个微妙的安全问题[4],连接合并在实际中很难正确操作[5]。即使你能够正确设置,最后也可能仍然获得两个分开的连接。但这并不总是坏事。首先,由于优先级和多路复用糟糕的实现(见下文),单一连接很容易比两个或者多个连接更慢[6];第二,由于拥塞控制器的竞争,使用过多的连接有可能会导致早期丢包。使用较少的连接(仍然多于一个)可以平衡拥塞增长和更好的性能,尤其在高速网络上。因此,我相信少量的sharding仍是一个不错的主意(如2~4个连接),即使使用的是HTTP/2。事实上,我认为大部分现代HTTP/2设置在性能上表现不错,因为它们的关键路径中仍然有一些额外的连接和第三方负载。
3. 资源打包和内联(Resource Bundling and Inlining)
在HTTP/1.1中,每个连接只能有一个活跃资源,进而导致了HTTP级别的队头阻塞[7]。因为连接数量被限制在仅仅6~30个,所以资源打包(其中较小的子资源被合并到一个更大的资源中)长期以来都是最佳实践。今天我们仍然在Webpack等打包器中看到这种操作。同样,资源也曾经常被内联在其他资源中(比如,关键CSS被内联在HTML)。
然而通过HTTP/2,单一连接可以多路复用多个资源,那么你就会有各种文件的大量未完成请求(outstanding request)(换言之,单一请求不再占据你的前几个宝贵连接)。这最初被解读为:“我们不再需要为HTTP/2打包或内联资源”。很多人吹捧这种方法要优于细粒度缓存(fine-grained caching),因为子资源可以被单独缓存,而且如果其中一个资源发生变化,则无需重新下载完整打包。这是真的,但却受到一定限制。
比如,你可以降低压缩效率[8],因为这样做可以更好地处理更多数据。此外,每个额外的请求或文件需要由浏览器和服务器处理,所以它们都有固有开销[9]。这些开销相当于:几百个小文件和几个大文件相当。在我们的早期测试中[10],我发现大幅减少了约40个文件。虽然现在这个数字可能会高一点[11],但HTTP/2中文件请求的成本依然没有如预测的那样低。最后,由于需要请求文件,所以不内联资源会增加延迟成本。内联与优先级、服务器推送(见下文)一起使用,意味着即使在今天你也可以通过内联一些关键CSS改善你的状况[12]。也许有一天,资源打包提案[13]将会对此有所帮助(但目前还没有)。
所有这一切对HTTP/3也适用。尽管如此,我依然读到有些人声称QUIC更适用于小文件传输,因为更多并发活跃的独立流意味着可以从队头阻塞消除特性中获益更多(我们在第二部分讨论过)。我认为这种说法有一些道理,但正如我们在第二部分所讲,这是一个非常复杂的问题,其中有很多可变参数(互相影响的参数)。我认为这些好处并不会多于我们讨论过的其他成本,而且仍需要更多研究(一个离谱的想法是:将每一个文件的大小精确到适合单一QUIC数据包,完全绕过队头阻塞。任何实现资源打包器的公司如果想要这么做,请付我专利费,哈哈)。
4. 优先级(Prioritization )
想要在单一连接上下载多个文件,你需要多路复用它们。正如第二部分所述,在HTTP/2中,这种多路复用由其优先级系统控制,这也是同一连接上拥有尽可能多的资源如此重要的原因:能够合理对这些资源进行优先级排序!然而,我们还看到,这一系统非常复杂,在实际中经常无法正确使用和实现[14](见下图)。因此,也意味着其他用于HTTP/2的建议[如因为请求成本低而减少打包,以及为了充分利用单一连接而减少服务器sharding(见上文)]也被证明在实际中表现不佳。
实现不当的HTTP/2协议栈会导致高优先级资源(底下两个)被延迟到其他低优先级下载(其他所有)之后。(图片来源:Andy Davies[15])
遗憾的是,作为普通Web开发者的你在这些事上也无能为力,因为它主要是浏览器和服务器本身的问题。不过,你可以通过不使用过多单一文件(将会降低优先级竞争的可能性)和仍然使用(受限的)sharding来缓解这一问题。另一个选择是使用多种影响优先级的技术,比如延迟加载[16](lazy loading)、JavaScript异步加载[17],以及preload(预加载)[18]等资源提示。这些技术在内部改变了资源的优先级,以便它们或早或晚被发送。不过,这些机制可能(确实)存在bug[19]。此外,不要指望在一堆资源上预加载就能提高速度:如果所有资源都突然变成高优先级,那么就不存在高优先级!使用预加载这样的技术[20]实际上很容易[21]延迟[22]关键资源。
我们同样在第二部分解释过,HTTP/3从根本上改变了这个优先级系统的内部结构。我们希望这意味着它的实际部署将出现较少的bug和问题,至少其中的某些问题应该被解决。不过我们并不确定,因为目前完整实现这一系统的HTTP/3服务器和客户端还比较少[23]。尽管如此,优先级的基本概念将不会改变。如果不能真正理解内部发生的情况,你将依然无法使用预加载等技术,因为它有可能会错误地排序你的资源。
5. 服务器推送和初次加载(Server Push and First Flight)
服务器推送允许服务器不用先等待来自客户端的请求即可发送响应数据。理论上,这种操作听起很棒,而且可以代替内联资源(见上文)。不过,我们曾在第二部分讨论过,由于拥塞控制、缓存、优先级和缓冲等问题[24],推送很难正确使用。总的来说,除非你真正了解自己在做的事,否则最好不要将它用于网页加载;即使这样做了,它很有可能也只是微优化[25]。不过,我仍然相信它在(REST)API中的作用,其中你可以在预热连接上推送(JSON)响应中链接的子资源[26]。这同样适用于HTTP/2和HTTP/3。
来总结一下,我认为对于TLS会话恢复和0-RTT,无论是在TCP+TLS还是QUIC之上,都可以给出相似的评论。我们在第二部分讨论过,在努力加速网页加载的最初阶段,0-RTT和服务器推送(正如它常用的那样)的作用类似。然而,那也意味着它们在当时同样都会受到限制(因为安全问题,QUIC甚至受限更多)。因此,微优化是你可能需要在底层进行各种微调并从中真正获益的方法。无法想象我曾经非常激动地尝试将服务器推送和0-RTT结合起来。
这一切意味着什么?
上文所述可以总结为一条经验法则:应用大部分你可以在网上找到的常见HTTP/2使用建议,但不要走极端。
下面是一些适用于HTTP/2和HTTP/3的具体建议:
-
在关键路径上将资源shard到1~3个连接上(除非你的用户主要分布在低带宽网络上),在需要时使用preconnect 和 dns-prefetch [27]。 -
按照变化频率、路径或者特性逻辑来打包子资源。每页510个JavaScript和510个CSS就可以了。内联关键CSS仍然是一个很好的优化。 -
(谨慎)使用复杂的特性,比如预加载。 -
使用可以妥善支持[28]HTTP/2优先级的服务器。对于HTTP/2,我推荐H2O[29]。Apache和NGINX通常也可以(尽管可以做得更好一些[30]),但应避免[31]使用Node.js。而HTTP/3的情况目前还不明朗(见下文)。 -
确保你的HTTP/2 Web服务器启用了TLS 1.3。
如你所见,虽然很难,但HTTP/3(和HTTP/2)的网页优化并没有那么高深莫测。而更难的是正确设置HTTP/3服务器、客户端和工具。
服务器和网络
现在你可能已经了解,QUIC和HTTP/3都是非常复杂的协议。如果你想要从零开始实现它们,你需要阅读(并理解!)七个文档以上的数百页内容[32]。幸好过去五年中很多公司已经在研究开源QUIC和HTTP/3实现[33],所以我们现在已经有了几个成熟且稳定的选项。
其中一些最重要、最稳定的选项如下所示:
语言 | 实现 | Python | aioquic[34] | Go | quic-go[35] | Rust | Quiche[36] (Cloudflare)、 Quinn[37]、Neqo[38](Mozilla) | C 和 C++ | mvfst [39](Facebook)、MsQuic[40]、(Microsoft)、 (Google) ngtcp2[41]、LSQUIC [42](Litespeed)、picoquic[43]、quicly [44](Fastly) |
然而,许多(也许是大多数)此类实现主要处理HTTP/3和QUIC事务;它们本身并不是真正成熟的Web服务器。而典型服务器(想想NGINX、Apache、Node.js)的发展有些缓慢,其中主要有几个原因:第一,其开发人员很少从一开始就参与到HTTP/3的开发中,现在他们必须迎头赶上。为了绕过这一问题,很多人在内部将上文列出的实现作为库来使用,但即使如此集成也非常困难。
其次,很多服务器依赖第三方TLS库(比如OpenSSL),这是因为TLS非常复杂且必须安全,所以最好重用现有、已验证过的操作。然而,虽然QUIC集成了TLS 1.3,但所使用的方法与TLS和TCP之间的交互非常不同。这意味着TLS库必须提供QUIC专用的API[45],但API的开发人员长期以来并不愿意这样做或者进度缓慢。OpenSSL是其中比较突出的问题,它已经推迟了QUIC支持[46],但许多服务器还在使用它。这一问题不断恶化,所以Akamai决定启动一个OpenSSL 的QUIC专用fork——quictls[47]。虽然还有其他选项和解决方法[48],但TLS 1.3对QUIC的支持仍然是很多服务器的障碍,并预计会持续一段时间。
下面是你能够开箱即用的完整Web服务器(和当前的HTTP/3支持一起)的部分名单:
-
Apache 目前支持尚不明朗。还没有宣布任何支持。它可能还需要OpenSSL。(注意,它有一个Apache Traffic Server 实现[49])。 -
NGINX[50] NGINX是定制化实现。它的实现时间不长,依然处于高度试验阶段。它有望在2021年底合并到NGINX主线[51]。注意,还有一个补丁可以在 NGINX上运行Cloudflare 的quiche库[52],它目前可能更稳定。 -
Node.js Node.js内部使用ngtcp2库。Node.js被OpenSSL进程所阻止[53],尽管它计划切换到QUIC-TLS fork以提升运行速度[54]。 -
IIS 目前支持尚不明朗,且没有宣布任何支持。不过它将有可能内部使用MsQuic库。 -
Hypercorn Hypercorn[55]集成了aioquic,试验性支持HTTP/3。 -
Caddy Caddy[56]使用quic-go,全面支持HTTP/3。 -
H2O H2O[57]使用quicly,全面支持HTTP/3。 -
Litespeed Litespeed[58]使用LSQUIC,全面支持HTTP/3。
请注意一些重要的微妙差异:
-
即使“全面支持”也只是说“尽其所能”,而非“生产就绪(production-ready)”。比如,许多实现还没有全面支持连接迁移、0-RTT、服务器推送或HTTP/3优先级。 -
据我所知,其他没有列出的服务器(比如Tomcat)也没有宣布支持。 -
在列出的Web服务器中,只有 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。相反,他们特别希望使用QUIC over UDP。问题是,QUIC几乎完全加密。其中包括QUIC级的元数据,如数据包序号以及表示连接关闭的信号。对于TCP,防火墙会主动跟踪所有元数据并检查其预期行为。(我们是否在数据包之前看到了完整握手?数据包是否遵循预期模式?有多少打开的连接?)我们曾在第一部分看到,这正是TCP实际不再进化的原因。不过,由于QUIC的加密,防火墙无法再像TCP时那样跟踪这些元数据,它们能够检查的数据也相对复杂。
因此,许多防火墙厂商目前建议阻止QUIC,直到他们能够升级软件。即使在这之后,很多公司也可能不希望使用QUIC,因为QUIC支持的防火墙下,他们能够使用的特性要远少于TCP时期。
连接迁移特性使情况变得更加复杂。如我们所见[59],这一特性允许使用CID从新的IP地址上继续连接,而无需进行新的握手。不过,对于防火墙而言,这看起来像是没有先经过握手就使用了新连接——很有可能是攻击者发送的恶意流量。防火墙无法使用QUIC的CID,因为它们为了保护用户隐私会不断发生变化!因此,服务器将需要就CID问题与防火墙通信,但目前还无法实现。
大规模设置的负载均衡器也面临类似的困扰。这些机器将传入的连接分布在大量的后端服务器上。一个连接的流量必须总是被发送到相同的后端服务器(其他服务器根本不知道如何处理它)。对于TCP来说,它可以基于四元组[60]实现这一点,因为它从不变化。但如果是QUIC连接迁移,这就不再是一种选择[61]。为了确定发送路线,服务器和负载均衡器将需要在选择哪些CID上达成一致。与防火墙配置不同的是,已经有提案[62]对此进行设置(尽管远未获得广泛实现)。
最后,还有其他更高级别的安全问题需要考虑,这些问题主要围绕0-RTT和DDoS攻击。我们在第二部分曾讨论过,QUIC已针对这些问题提供了一些缓解措施,但理想情况下,它们将在网络上使用额外防线。比如,为了防止重放攻击[63],代理或边缘服务器也许会阻止某些0-RTT请求到达实际后端。此外,为了防止反射攻击,或仅发送第一个握手数据包就停止回复(在TCP中被称为SYN flood[64])的DDoS攻击,QUIC包含了重试特性( retry feature)[65]。服务器因此可以不用维护复杂的状态来检验行为正常的客户端(相当于TCP SYN cookie[66])。当然,这个重试过程最好发生在到达后端服务器之前的某个位置,比如在负载均衡器。但同样需要额外的配置和通信来设置[67]。
以上这些只是网络和系统管理者在使用QUIC和HTTP/3时将面临的突出问题。我还介绍过其他几个问题[68]。QUIC RFC还有两个单独[69]的随附文档[70]也讨论了这些问题,并给出了可能(部分)的缓解措施。
这一切意味着什么?
HTTP/3和QUIC都是非常复杂的协议,并依赖于很多内部机制。尽管已经存在一些在后端部署新协议的选项,但还未到全面部署的最佳时间。那些最重要的服务器和底层库(比如OpenSSL)很可能将花上几个月甚至几年的时间进行更新。
即使如此,为了安全以及能够充分使用这些协议,正确配置服务器和其他网络中间件在大规模设置中非常重要。你需要一个优秀的开发和运营团队来帮助你正确过渡。
因此,特别是在早期,最好依赖大型托管公司或CDN公司来为你设置和配置协议。我们在第二部分讨论过,使用这种方法,QUIC才最有可能成功,且使用CDN能够帮助你优化关键性能。我个人推荐Cloudflare或Fastly,因为这两家公司都一直在密切参与标准化工作,并将拥有最先进、且获得最佳调整的可用实现。
注释:
[1] https://www.tunetheweb.com/
[2] https://www.manning.com/books/http2-in-action
[3] https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/
[4] https://jakearchibald.com/2017/h2-push-tougher-than-i-thought/#requests-without-credentials-use-a-separate-connection
[5] https://nooshu.github.io/blog/2019/12/17/http2-and-sri-dont-always-get-on/
[6] https://twitter.com/zachleat/status/1055219667894259712
[7] https://calendar.perfplanet.com/2020/head-of-line-blocking-in-quic-and-http-3-the-details/
[8] https://jakearchibald.com/2021/f1-perf-part-7/#lots-of-little-resources-vs-one-big-resource
[9] https://twitter.com/yoavweiss/status/1254650804524507136
[10] https://speeder.edm.uhasselt.be/webist/files/h2bestpractices_RobinMarx_WEBIST2017.pdf
[11] https://twitter.com/youyuxi/status/1425933472871456777
[12] https://calendar.perfplanet.com/2020/implementing-critical-css-from-cms-to-cls/
[13] https://github.com/WICG/bundle-preloading
[14] https://github.com/andydavies/http2-prioritization-issues
[15] https://github.com/andydavies/http2-prioritization-issues
[16] https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading
[17] https://addyosmani.com/blog/script-priorities/
[18] https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload
[19] https://bugs.chromium.org/p/chromium/issues/detail?id=788757
[20] https://www.debugbear.com/blog/rel-preload-problems
[21] https://twitter.com/programmingart/status/1351557858354225159
[22] https://twitter.com/csswizardry/status/1349681832393109510
[23] https://qlog.edm.uhasselt.be/epiq/files/QUICImplementationDiversity_Marx_final_11jun2020.pdf
[24] https://calendar.perfplanet.com/2016/http2-push-the-details/
[25] https://twitter.com/patmeenan/status/1359337536125075460
[26] https://github.com/dunglas/vulcain
[27] https://www.smashingmagazine.com/2019/04/optimization-performance-resource-hints/
[28] https://github.com/andydavies/http2-prioritization-issues
[29] https://github.com/h2o/h2o
[30] https://twitter.com/programmingart/status/1245397190194999297
[31] https://twitter.com/jasnell/status/1245410283582918657
[32] https://quicwg.org/
[33] https://github.com/quicwg/base-drafts/wiki/Implementations
[34] https://github.com/aiortc/aioquic
[35] https://github.com/lucas-clemente/quic-go
[36] https://github.com/cloudflare/quiche
[37] https://github.com/quinn-rs/quinn
[38] https://github.com/mozilla/neqo
[39] https://github.com/facebookincubator/mvfst
[40] https://github.com/microsoft/msquic
[41] https://github.com/ngtcp2/ngtcp2
[42] https://github.com/litespeedtech/lsquic
[43] https://github.com/private-octopus/picoquic
[44] https://github.com/h2o/quicly
[45] https://daniel.haxx.se/blog/2019/01/21/quic-and-missing-apis/
[46] https://www.openssl.org/blog/blog/2020/02/17/QUIC-and-OpenSSL/
[47] https://github.com/quictls/openssl
[48] https://daniel.haxx.se/blog/2021/04/02/where-is-http-3-right-now/
[49] https://cwiki.apache.org/confluence/display/TS/QUIC
[50] https://quic.nginx.org/
[51] https://www.nginx.com/blog/our-roadmap-quic-http-3-support-nginx/
[52] https://blog.cloudflare.com/experiment-with-http-3-using-nginx-and-quiche/
[53] https://github.com/nodejs/node/pull/37067
[54] https://github.com/nodejs/node/issues/38478
[55] https://pgjones.dev/blog/http-1-2-3-2019/
[56] https://ma.ttias.be/how-run-http-3-with-caddy-2/
[57] https://github.com/h2o/h2o
[58] https://github.com/litespeedtech/lsquic
[59] https://www.smashingmagazine.com/2021/08/http3-core-concepts-part1/#quic-supports-connection-migration
[60] https://www.smashingmagazine.com/2021/08/http3-core-concepts-part1/#quic-supports-connection-migration
[61] https://www.youtube.com/watch?v=pq_xk_Pecu4&t=1226s
[62] https://datatracker.ietf.org/doc/html/draft-ietf-quic-load-balancers
[63] https://blog.cloudflare.com/introducing-0-rtt/#whatsthecatch
[64] https://en.wikipedia.org/wiki/SYN_flood
[65] https://www.rfc-editor.org/rfc/rfc9000.html#name-address-validation-using-re
[66] https://en.wikipedia.org/wiki/SYN_cookies
[67] https://datatracker.ietf.org/doc/html/draft-ietf-quic-load-balancers-06#section-7
[68] https://www.youtube.com/watch?t=981&v=pq_xk_Pecu4&feature=youtu.be
[69] https://datatracker.ietf.org/doc/html/draft-ietf-quic-applicability
[70] https://datatracker.ietf.org/doc/html/draft-ietf-quic-manageability
作者简介:
Robin Marx: IETF贡献者、HTTP/3和QUIC工作组成员。2015年,作为PhD的一部分,Robin开始研究HTTP/2的性能,这使他后来有机会在IETF中参与HTTP/3和QUIC的设计。在研究这些协议的过程中,Robin开发了QUIC和HTTP/3的调试工具(被称为qlog和qvis),目前这些工具已经使来自世界各地的许多工程师受益。
致谢:
本文已获得_Smashing Magazine_和作者Robin Marx的授权翻译和发布,特此感谢。
|