QUIC之拥塞控制和0-RTT连接建立

2022-09-14 19:46:21 浏览数 (1)

 点击上方“LiveVideoStack”关注我们

▲扫描图中二维码或点击阅读原文▲

了解音视频技术大会更多信息


翻译、编辑:Alex 技术审校:刘连响 本文来自Smashing Magazine,原文链接: https://www.smashingmagazine.com/2021/08/http3-performance-improvements-part2/

QUIC

Robin讲HTTP/3

#003#

速览: 在经过五年的开发之后,新的HTTP/3协议终于接近尾声。让我们一起深入了解HTTP/3的性能提升、拥塞控制、队头阻塞和0-RTT连接建立。

欢迎回到HTTP/3协议系列文章。在第一部分,我们了解了为什么我们需要HTTP/3(从0到1讲解HTTP/3)、底层QUIC协议以及它的主要新特性。

在第二部分,我们将聚焦QUIC和HTTP/3为网页加载带来的性能提升。不过,我们对这些新性能实际产生的预期影响持保留意见。

正如我们将看到的,QUIC和HTTP/3确实具备提升网络性能的巨大潜力,但主要针对网速较慢的用户。如果你的平均访客使用的是较快的有线或者蜂窝网络,那他们很可能就不会从新的协议中获益太多。不过请注意,通常即使在上行链路很快的国家或地区,网速最慢的1%甚至是10%用户(即所谓的第99或者第90个百分位)依然很可能受益良多。这是因为HTTP/3和QUIC主要帮助处理当今互联网上那些并不常见却潜在影响很大的问题。

虽然第二部分将真正深入的技术内容交给了外部资源,主要解释这些协议对于普通Web开发者至关重要的原因,但与第一部分相比,它的技术性还是比较强

网速入门课

讨论性能和“速度”是一件很复杂的事,因为导致网页加载速度“缓慢”的潜在原因有很多。我们今天涉及的是网络协议,所以我们将从网络的角度来了解这些原因,而其中最重要的两个便是延迟和带宽。

延迟可简略定义为从A点(如客户端)发送数据到B点(如服务器)所需的时间。它在物理上受限于光速,或(实际)信号在电线中或者室外的传播速度。这意味着延迟常常取决于物理世界中两点A与B之间的距离。

在地球上[1],这意味着通常延迟(概念上)会很小,约在10~200毫秒之间。不过这只是单向延迟:对数据包的响应也需要返回。双向延迟通常被称为往返时间RTT,round-trip time)。

由于拥塞控制等特性(见下文),我们将经常需要相当多的RTT来加载一个文件。因此,低于50ms的低延迟不断叠加,就会造成很大的延迟。这就是CDN(content delivery network,内容分发网络)存在的主要原因:为了尽量减少延迟,它们将服务器放在靠近终端用户的物理位置。

带宽可以简略定义为同时发送的数据包数量。这里不太好解释,因为它取决于介质的物理属性(比如,所使用的无线电波的频率)、网络上的用户数量,以及连接不同子网(因为它们通常每秒只能处理一定数量的数据包)的设备。

输送水的管道是常用到的一个比喻:管道的长度是延迟,而宽度就是带宽。然而,在互联网上,我们通常会有很多由多个管道连接起来的长管道,而其中一些要更宽(这就形成了最窄连接处的瓶颈)。因此,A点与B点之间的端到端带宽常常受限于最慢的子网。

虽然在接下来的讲解中并不需要充分理解这些概念,但是如果你想要了解这方面的更多信息,我推荐你阅读Ilya Grigorik的High Performance Browser Networking一书中介绍延迟和带宽的章节[2]。

拥塞控制

传输协议如何高效利用网络的全部(物理)带宽(大致来说,就是每秒发送和接收的数据包数量)是性能很重要的一方面。它反过来会影响网页下载资源的速度。有人声称QUIC在这方面比TCP做得好,但事实并非如此。

你知道吗?

比如,TCP连接不会以满带宽的方式发送数据,因为很可能会导致网络过载(或者拥塞)。正如我们之前所说,这是因为每个网络链接每秒只能(物理)处理特定数量的数据。如果给它的数据包太多,它除了丢掉多出的数据包之外别无选择,这就导致了丢包。

我们在第一部分(从0到1讲解HTTP/3)讨论过,对于TCP这样的可靠协议,恢复丢包的唯一方法就是重传一份新的数据备份(需要一个RTT)。特别是在高延迟的网络上(比如,超过50ms RTT的网络),丢包会严重影响网络性能。

另一个问题是,我们无法提前知道最大带宽将是多少。它通常取决于端到端连接中的瓶颈,但是我们无法预测或知道它的所在。目前互联网也没有任何向端点发送链路容量信号的机制。

除此之外,即使我们知道可用的物理带宽,也并不意味着我们就都能使用。通常几个用户同时在网络上活跃,他们每一个都需要公平共享可用带宽。

因此,连接并不知道它可以预先(安全且公平)使用多少带宽,以及带宽会随着其他用户的加入、离开和使用而发生变化。为了解决这个问题,TCP通过使用一种被称为“拥塞控制(congestion control)”的机制来不断尝试发现可用带宽。

在连接开始处,它发送一些数据包(实际为10~100个数据包,或者约14~140KB的数据)并等待一次往返,直到接受方发送回这些数据包的确认。如果所有数据包都被确认,便意味着网络可以应对此发送速率,我们可以尝试使用更多数据来重复这个过程(实际情况是,通常发送速率每次迭代都会翻倍)。

这样一来,发送速率就会持续增加直到一些数据包无法确认(指丢包和网络拥塞)。第一阶段通常被称为“慢启动(slow start)”。一旦检测到丢包,TCP就会降低发送速率,(一段时间之后)再增加发送速率,尽管增量要小得多。这种先减再增的逻辑会在每次丢包后不断重复。最终,这意味着TCP会一直努力达到它理想且公平的带宽份额。图1说明了这种机制。

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

上面是对拥塞控制的一个极其简化的解释。在实际中,还要受到很多其他因素的影响,比如bufferbloat[3]、拥塞导致的RTT波动[4],以及多个并发发送方需要获得公平的带宽份额这一事实[5]。因此,存在许多不同的拥塞控制算法,今天依然有很多算法被开发出来,但没有一种算法可以在所有场景中都获得最佳表现。

虽然TCP的拥塞控制使其更强健,但也意味着需要时间达到最佳发送速率(取决于RTT和实际可用带宽)。对网页加载来说,这种慢启动方法也会影响FCP(first contentful paint,第一次内容绘制)等指标,因为只有少量数据(几十KB到几百KB)可以在最初的几次往返中被传输(你也许听说过将你的关键数据保持在14KB以内的建议[6])。

选择一种更加激进的方式也可以在高带宽和高延迟的网络上获得更好的效果,特别是当你不在意偶尔的丢包时。这里我再次看到了许多关于QUIC工作原理的误解

我们在第一部分(HTTP/3核心概念之QUIC)讨论过,理论上丢包(以及相关的队头阻塞)对QUIC影响较小,这是因为QUIC可以独立处理每个资源的字节流的丢包。此外,QUIC运行在UDP之上。UDP与TCP不同,没有内置拥塞控制特性;同时它允许你以任何速率传输你想传输的数据且不会重传丢失数据。

因此很多相关文章声称QUIC也不会使用拥塞控制,而是以比UDP(依靠队头阻塞消除来处理丢包)高得多的速率开始重新发送数据,这就是QUIC比TCP快很多的原因。

实际上,事实并非如此[7]:QUIC其实使用了与TCP非常相似的带宽管理技术。它同样以较低的速率开始,随着时间推移不断提高速率,并将确认作为测量网络容量的关键机制。原因是(除其他原因外):一、为了对HTTP等有用,QUIC需要可靠;二、QUIC需要对其他QUIC(和TCP!)连接公平;三、消除队头阻塞实际上并不能很好地防止丢包(我们将在下文了解)。

不过,这并不表示QUIC不能在管理带宽方面比TCP更智能(一点)。主要原因是QUIC比TCP更加灵活,且更易进化。正如我们前文所述,如今仍然在大力开发拥塞控制算法,我们将很有可能(比如)需要调整这些算法使其充分利用5G[8]。

而TCP通常在操作系统内核中实现,这是一个安全却更受限制的环境(对于大多数操作系统来说不是开源的)。因此,通常只有少数几个开发者会调整拥塞逻辑,且进化缓慢。

相比之下,大部分QUIC实现目前在“用户层(我们通常运行native应用的地方)”完成而且开源[9]——明确鼓励更广泛的开发者进行试验(如Facebook所示[10])。

另一个具体的例子是QUIC的延迟ACK频率(https://datatracker.ietf.org/doc/html/draft-iyengar-quic-delayed-ack-02)拓展提案。虽然默认情况下,QUIC每收到两个数据包发送一个确认,但此扩展允许端点确认,比如,每10个数据包发送一个确认。目前显示在卫星和非常高带宽的网络上,这会带来很大的速度优势,因为降低了重传确认数据包的开销。将这一扩展添加在TCP上将耗费相当长的时间才能被采用,但对于QUIC,部署起来就容易多了。

因此,我们希望随着时间的推移,QUIC的灵活性能够带来更多的试验并促进更好的拥塞算法的开发,转而能够将它们向后移植(backport)到TCP上进而改进它。

你知道吗?

官方的QUIC Recovery RFC 9002[11]中规定了NewReno拥塞控制算法的使用。虽然这种方法很强大,但却有些过时,而且不再在实际中广泛应用。但它为什么会出现在QUIC RFC中呢?第一个原因就是,当启动QUIC时,NewReno是最新的已标准化的拥塞控制算法,而其他高级算法(如BBR 和CUBIC),要么还没有被标准化,要么在最近[12]才成为RFC。

第二个原因是,NewReno的设置相对简单。因为需要调整算法来处理QUIC与TCP之间的差异,所以使用更简单的算法更容易解释这些变化。因此,RFC 9002应解读为“如何使拥塞控制算法适用于QUIC”,而不是“这些算法应该用于QUIC”。确实,大部分生产级的QUIC实现已经自定义了CUBIC[13]和BBR[14]的实现。

值得强调的是,拥塞控制算法并不仅限定于TCP或QUIC,任何协议都可以使用它们。希望QUIC的进步最终也能够体现在TCP协议栈。

你知道吗?

注意,拥塞控制旁有一个相关概念被称为流量控制[15](flow control),这两个特性在TCP中常被混淆,因为据说它们都使用“TCP窗口(TCP window)”,虽然实际上是有两个窗口:拥塞窗口和TCP接收窗口。流量控制在我们所感兴趣的页面加载场景中并不常用,所以这里我们略过不提。这里[16]提供[17]更多深入信息[18]。

这一切意味着什么?

QUIC仍然被物理定律所限制,而且需对互联网上的其他发送方友好。这意味着与TCP相比,它不会神奇般以快得多的速度下载网站资源。不过,QUIC的灵活性意味着试验新的拥塞算法将会变得更容易,这将在未来同时改进TCP和QUIC。

0-RTT连接建立

QUIC的第二个性能方面是关于在新的连接上发送有用的HTTP数据(比如网页资源)前需要多少个RTT。有人说QUIC比TCP TLS快两个甚至三个RTT,但我们将看到实际上只快一个RTT。

你知道吗?

我们在第一部分说过,在HTTP请求和响应交换之前,连接通常进行一次(TCP)或者两次(TCP TLS)握手。为了(比如)加密数据,客户端和服务器都需要知道这些握手交换的初始参数。

在下面图2中你可以看到,每个独立的握手至少需要一个RTT完成(TCP TLS 1.3, (b)),有时是两个RTT(TLS 1.2 和之前 (a))。这很低效,因为在发送第一个HTTP请求之前,我们至少需要两个握手往返等待时间(开销),这意味着我们至少要等待三个RTT才能收到第一个HTTP响应数据(返回红色箭头)。在较慢的网络上,这意味着100ms~200ms的开销。

图2:TCP TLS vs. QUIC连接建立

你也许想知道为什么TCP TLS握手无法简单地合并,并在同一个RTT中完成。这在概念上也许可以实现(QUIC正是这么做的),但TCP最初并不是这样设计的,因为不管上方有没有TLS[19],我们都需要能够使用TCP。换句话说,TCP在握手期间根本不支持发送非TCP内容。虽然曾经想通过TCP Fast Open扩展来添加这一特性,但正如我们在第一部分讨论过的,事实证明很难大规模部署[20]。

幸好QUIC在一开始设计时就考虑到了TLS,因此可以将传输和加密握手合并到一个机制中。这意味着QUIC握手仅需一个RTT就可以完成,要比TCP TLS 1.3少一个RTT(见上图2c)。

你现在也许很困惑,因为你之前很可能读到过:QUIC要比TCP快两个甚至三个RTT,而不止一个。这是因为大部分文章只考虑了最差的情况(TCP TLS 1.2, (a)),更不用说现代TCP TLS 1.3也“仅”需要两个RTT(很少显示(b))。虽然一次往返对提升速度很有帮助,但也没那么厉害。尤其是在网速比较快的情况下(比如,小于50ms的RTT),几乎很难发现其中的差别,显然较慢的网络和较远的服务器连接受益显著。

接下来,你也许想知道我们需要等待握手的原因。我们为什么不在第一次往返时就发送HTTP请求?这主要是因为,如果我们这么做了,那么第一个请求在发送时就无法加密,任何线路上的窃听者都会读取数据,这显然会危害到隐私和安全。因此,在发送第一个HTTP请求之前,我们需要等待加密握手完成。或者我们真的需要吗?

实际中这里用到了一个巧妙的技巧。我们知道用户经常会在短时间内重新访问网页(第一次访问时)。因此,我们可以在未来使用初始加密连接( initial encrypted connection)引导第二个连接。简单来说,在其生命周期的某个时刻,第一个连接用于在客户端和服务器间安全地传递新的密码参数,然后这些参数再被用于加密第二个连接(从一开始),而无需等待完整的TLS握手完成。这种方法被称为“会话恢复(session resumption)”。

这使得我们有机会进行有效的优化:我们现在可以将第一个HTTP请求和QUIC/TLS握手一起安全地发送,节省了另一个RTT!对于TLS 1.3,这有效地去掉了TLS握手等待时间。这种方法也被称为0-RTT(当然,虽然HTTP响应数据开始到达仍需要一次往返)。

会话恢复和0-RTT是我经常看到的被人错误解释的QUIC特定特性。实际上,它们都是TLS特性:已经在TLS 1.2中以某种形式存在,并在TLS 1.3[21]中已完全成熟。

换言之,如下图3所示,我们也可以通过TCP(HTTP/2甚至是HTTP/1)获得这些特性的性能优势!我们看到即使使用了0-RTT,QUIC依然只比运行最佳的TCP TLS 1.3协议栈快一个RTT。那些称QUIC比图2(a)和图3(f)快三个RTT的说法(如我们所见)有失公允。

图3:TCP TLS vs. QUIC 0-RTT连接建立

最糟糕的地方是,当使用0-RTT时,由于安全性,QUIC甚至无法很好地利用增加的这个RTT。为了理解这点,我们需要理解TCP握手存在的原因之一。首先,它需要客户端在给服务端发送数据之前确定服务端是可用的(在向它发送高层数据之前)。

其次(很关键的一点),它允许服务器确保打开连接的客户端是它们在发送数据前所说的客户端及其位置。如果你回想我们在第一部分(HTTP/3核心概念之QUIC)定义连接所使用的四元组,你就会知道客户端主要由IP地址识别。这里就是问题所在:IP地址具有欺骗性

假设攻击者通过HTTP over QUIC 0-RTT请求了一个非常大的文件,但他们使用了虚假的IP地址,使它看上去像是来自受害者电脑的0-RTT请求。图4所示。QUIC服务器无法检测到是否为虚假IP地址,因为这是它从那个客户端看到的第一个(批)数据包。

图4:在向QUIC服务器发送0-RTT请求时攻击者可以使用虚假的IP地址,这会造成对受害者的放大攻击。

如果服务器随后只是开始将大文件发送回虚假的IP地址,那么很可能造成受害者网络带宽过载(尤其当攻击者并行发送许多这样的假请求时)。注意,受害者会丢掉QUIC响应,因为它并不期待传入的数据,但这不要紧:它们的网络仍然需要处理数据包!

这被称为反射(reflection)或者放大攻击(amplification attack)[22],它是黑客进行DDoS攻击的主要方式。注意,攻击并不会发生在使用0-RTT over TCP TLS时,这是因为需要在发送0-RTT请求甚至与TLS握手一起发送前先完成TCP握手。

因此,QUIC必须保守地回复0-RTT请求,限制发送的响应数据量,直到客户端被验证是真正的客户端而不是受害者。对于QUIC来说,这个数据量已被设置为从客户端接收到的数据量的三倍[23]。

换言之,QUIC的最大“放大系数”为3,这一系数被认为在实用性能与安全风险间达到了可接受的平衡(尤其与某些放大系数超过51000倍的事件相比[24])。由于客户端通常先发送一到两个数据包,QUIC服务器的0-RTT回复将被限制在仅4 到 6 KB(包括其他QUIC和TLS开销!),这里没什么特别之处。

除此之外,其他安全问题也会导致限制HTTP请求类型的重放攻击(replay attacks)。比如,Cloudflare只允许0-RTT中不带查询参数的HTTP GET请求[25]。这进一步限制了0-RTT的用途。

幸好,QUIC具备改善这一情况的选项。比如,服务器可以检查0-RTT是否来自之前与其有效连接的IP[26]。不过,只有客户端存在于同一网络时才有效(在某种程度上限制了QUIC的连接迁移特性[27])。即使有效,QUIC的响应也仍然被拥塞控制者的慢启动逻辑所限制(我们上文有讨论过)。所以,除了节省的一个RTT外,QUIC并没有获得额外的大幅度速度提升。

你知道吗?

有趣的是,QUIC的三倍放大限制对正常的非0-RTT握手过程(图2c)也有效。如果服务器的TLS证书[28]太大,导致4 到6 KB无法容纳,就会出现问题。这种情况下,就不得不拆分证书,第二个数据块必须等待第二次往返被发送(在第一批传入的数据包确认以后,表明客户端的IP是真实的)。这时,QUIC的握手可能仍需要两个RTT,等同于TCP TLS!对于QUIC来说,这就是证书压缩[29]等技术会额外重要的原因。

你知道吗?

某些特定高级设置足以缓解这些问题,以使0-RTT更有用处。比如,服务器可以记住客户端上次的可用带宽是多少,从而减少拥塞控制对重新连接(非虚假)客户端的慢启动的限制。学术界已经对此进行了研究[30],QUIC甚至已经有了扩展提案[31]。几家公司也在进行相关工作来加速TCP。

另一个选项会让客户端发送超过一到两个数据包(比如再发送七个带有填充的数据包),所以三倍的限制就会转换成一个更加有趣的12到14KB的响应(即使是在连接迁移之后)。我已经将其写入了我的一篇论文中[32]。

最终,(行为不当的)QUIC服务器如果认为安全或者不在意潜在的安全风险,那么它们也可以有意增加三倍限制(毕竟,并没有协议警察[33]来阻止)。

这一切意味着什么?

QUIC通过0-RTT建立快速连接实际上更像是一种“微优化”,而非革命性的新特性。与最先进的TCP TLS 1.3设置相比,它最多可以节省一次往返。出于一系列安全考虑,在第一次往返时所实际发送的数据量受到很多限制。

因此,如果你的用户使用的是具备很高延迟的网络(比如,超过200ms RTT的卫星网络)或者你通常不会发送太多数据,这一特性将会非常有效。大量缓存的网站,以及通过API和其他协议(如 DNS-over-QUIC[34])定期获取少量更新的单页应用程序是后者的例子。谷歌十分看好QUIC的0-RTT的原因是[35],它已经在其高度优化的搜索页面中测试了这一特性,其中查询响应非常少。

其他情况下,你最多只能再降低几十毫秒的延迟,即使已经使用了CDN(如果你在意性能的话,就应该使用CDN)。

注释:

[1] https://www.youtube.com/watch?v=6bbN48zCNl8

[2] https://hpbn.co/primer-on-latency-and-bandwidth/

[3] https://www.youtube.com/watch?v=ZeCIbCzGY6k

[4] https://blog.apnic.net/2017/05/09/bbr-new-kid-tcp-block/

[5] https://justinesherry.com/papers/ware-hotnets19.pdf

[6] https://www.tunetheweb.com/blog/critical-resources-and-the-first-14kb/

[7] https://www.rfc-editor.org/rfc/rfc9002.html

[8] https://dl.acm.org/doi/abs/10.1145/3387514.3405882

[9] https://github.com/quicwg/base-drafts/wiki/Implementations

[10] https://research.fb.com/wp-content/uploads/2019/12/MVFST-RL-An-Asynchronous-RL-Framework-for-Congestion-Control-with-Delayed-Actions.pdf

[11] https://www.rfc-editor.org/rfc/rfc9002.html

[12] https://datatracker.ietf.org/doc/html/rfc8312

[13] https://blog.cloudflare.com/cubic-and-hystart-support-in-quiche/

[14] https://qlog.edm.uhasselt.be/epiq/files/QUICImplementationDiversity_Marx_final_11jun2020.pdf

[15] https://www.rfc-editor.org/rfc/rfc9000.html#name-flow-control

[16] https://qlog.edm.uhasselt.be/epiq/files/QUICImplementationDiversity_Marx_final_11jun2020.pdf

[17] https://www.youtube.com/watch?v=HQ1uIClmzkU&t=603s

[18]

https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/

[19] https://www.smashingmagazine.com/2021/08/http3-core-concepts-part1/#there-is-no-quic-without-tls

[20] https://squeeze.isobar.com/2019/04/11/the-sad-story-of-tcp-fast-open/

[21] https://tools.ietf.org/html/rfc8446#section-2.3

[22] https://www.f5.com/labs/articles/education/what-is-a-dns-amplification-attack-

[23] https://www.rfc-editor.org/rfc/rfc9000.html#name-address-validation

[24] https://www.cloudflare.com/learning/ddos/memcached-ddos-attack/

[25] https://blog.cloudflare.com/introducing-0-rtt/#whatsthecatch

[26] https://www.rfc-editor.org/rfc/rfc9000.html#name-address-validation-for-futu

[27] https://www.smashingmagazine.com/2021/08/http3-performance-improvements-part2/#connection-migration

[28]https://hpbn.co/transport-layer-security-tls/#chain-of-trust-and-certificate-authorities

[29]https://www.fastly.com/blog/quic-handshake-tls-compression-certificates-extension-study

[30] https://arxiv.org/pdf/1905.03144.pdf

[31] https://datatracker.ietf.org/doc/html/draft-kuhn-quic-0rtt-bdp-08

[32] https://qlog.edm.uhasselt.be/epiq/files/QUICImplementationDiversity_Marx_final_11jun2020.pdf

[33] https://www.rfc-editor.org/rfc/rfc8962

[34] https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic

[35] https://storage.googleapis.com/pub-tools-public-publication-data/pdf/8b935debf13bd176a08326738f5f88ad115a071e.pdf

作者简介:

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的授权翻译和发布,特此感谢。


▼识别二维码或猛戳下图订阅课程▼

喜欢我们的内容就点个“在看”吧!

0 人点赞