译自:https://vt200.com/tcp-congestion-cubic-quic-bbr/[TCP Confusion Clearing: Congestion Control, CUBIC, QUIC, BBR]

我偶尔会从开发者和网络人员那里听到关于 TCP 拥塞窗口大小的讨论。我听到关于滑动窗口算法和基于 ACK 的窗口增长(慢启动)的闲聊,以及高 RTT 总是意味着批量下载慢。当然,你总是希望快速下载,但是却遇到了蜗牛般的下载速度,你感觉你的 TCP 参数出了问题,是时候打开 TCP 并对其进行调整了。但这种诱惑是错误的,我可能还会对普通工程师所做的调整感到恐惧。

实际上我发现大多数工程师对 TCP 的实际原理知之甚少。大多数工程师对滑动窗口和 ack-pacing 有所了解,其余的都是误解和混淆。所以,我希望能够减少这种混淆,并讨论真正影响你批量 TCP 下载速率的因素,即 CUBIC。我还会谈到 BBR:一个基于延迟的拥塞检测的 CUBIC 替代方案,以及 QUIC:一种新的网络传输协议。

cubic growing cwnd
  1. TCP CUBIC 流样本。在橙色点上,CUBIC 达到峰值并能够增长 cwnd,灰色点表示 Linux CUBIC 将 cwnd 收敛到 0*9*W_max(尽管容量下降,但是依然表示出 CUBIC 形状)

首先,关于调整的问题,你很可能不需要调整你系统的 TCP 参数。我能想到的只有两种可能需要调整的场景:

  1. 你是一个数据中新,并希望为低延迟网络采用 Data Center TCP

  2. 你是用 10Gig+ 的链路,并且不相信你的操作系统对高容量链路的自动调整。

除了这两种情况,你应该相信你系统自带的调整是可靠的。这是因为工程师在整个 ARPA/互联网的历史中一直在调整 TCP,而且每个人都认为 TCP 的行为正确性十分重要。

1989 年,当互联网任由政府运行且 TCP 还很年轻时,工程师们正在学习关于不良 TCP 行为和网络的知识。TCP 当时没有拥塞控制,偶尔会导致整个互联网的崩溃。客户端被编码为尊重连接另一端的机器的容量,而不是网络的容量。就像一只对裁判愤怒的篮球队,即使只有一小部分数据包能够通过,客户端也会反复向本地过载的路由器重传。1989 年,在一次特别严重的事故中,NSFNET 的骨干链路在难以置信的拥塞情况下,只有 40bps 的带宽(这是 32kbps 容量的 1/1000)。互联网正在遭受拥塞崩溃!解决拥塞崩溃的方法是将拥塞控制和 Jacobson 和 Karel 的慢启动算法注入到 TCP 中。

之前 TCP 只有一个“发送窗口”,每个客户端会跟踪对端准备从线路上读取的字节数。如果客户端没有准备接收数据,发送方就不会发送。正如 1986 年的崩溃所证明的那样,只有接收窗口是不够的。就像今天一样,TCP 连接不是完全由系统的内存或者处理能力限制的。瓶颈是网络容量。接收窗口处于完全开放状态,但是网络却陷入了崩溃。Jacobson 和他们的朋友引入了拥塞窗口,这规定了现在可以传输多少字节而不会有拥塞的风险。发送状态机会查看接收窗口和拥塞窗口,并只发送两者较小的一个的那么多数据包。

当一个 TCP 创建后,它并不知道它连接的链路有多达,它的吞吐量可能是 1kbps,也可能使 10Gbps。拥塞控制找到链路的容量以避免链路崩溃,并且需要快速找到这个容量(你不会想把所有时间都花费在加速上)。Jacobson 和他的朋友们选择了一个指数增长的系统,每收到一个 ACK 就会触发两个额外的数据包,为了让大家困惑,Jacobson 把这个指数增长称之为慢启动。感谢 Jacobson。

这里为 tweakers 辩护。确实存在这样的情况:调整 initial_cwnd 会影响慢启动达到速度的快慢。在 \(2^{4+n}\) 而不是 \(2^{1+n}\) 启动可以更快地达到更大的 cwnd。然而这并不需要调整。是的,慢启动是基于 RTT 速度的,这会影响高 RTT 的用户。然而,在当前(Linux)默认的 initial_cwnd 为 10 个数据包的情况下,TCP 可以在 14 个周期中增长到 122Mbps 的流量,对于 100ms 的链路这是 1.4s,在大量下载的背景下并不长(至于像 10 GigE 这样的高容量链路,大多数操作系统会自动调整 TCP 参数)。

一个相关的误解是我听到的(这促使了对 initial_cwnd 的调整),即慢启动及其由 ACK 触发的 2^x 增长是 TCP 传输速率的主要驱动因素。完全不是,慢启动只是开场戏,甚至更少。绝大多数时间 TCP 处于拥塞避免状态,由像 CUBIC 这样的算法驾驭。如果你想责备和预期不符的大流量下载,不要诅咒慢启动或者 ACK-pacing,埋怨 CUBIC 吧。(当然也要赞美它,它真的很酷!)。

cubic basic up down stream.thumbnail

如上图所示,我们的 TCP 流从 ACK 驱动的指数增长开始,知道它压倒链路并导致丢包(图中的第一个峰值)。然后拥塞控制开始接管流(连接的剩余部分)。拥塞控制指令:在链路容量附近操作流,防止崩溃,并与其它流友好相处。

1988 年,我们拥塞控制的核心是 Jacobson 的新算法:Tahoe,Tahoe 是一个真正的进步,然而我会描述 CUBIC 而不是 Tahoe。CUBIC 是一个现代的、由 Linux 内核默认使用的拥塞控制算法。

当 CUBIC 接管 TCP 流时,它知道 W_max:当 TCP 使链路过载时 cwnd 的值。CUBIC 假设这是链路容量在 W_max 附近。从更大的角度来看,CUBIC 希望利用已知的链路容量,然后偶尔超过这个安全的吞吐量以防止链路容量增加。下面的公式是显示的 cubic 函数。CUBIC 在丢包时回退到 0.8\*W-max,然后在 W_max 出绘制这个立方函数,其拐点(平坦部分),从上图可以看出,前三个峰值是接近拐点中断的 cubic 函数,看起来像鲨鱼鳍。最后,你可以在第四个周期看到容量增加。流通过拐点(W_max)进入 cubic 函数的“最大探测”区域。出现了一个清晰的立方形式。经过几个鲨鱼鳍周期后,这种增长再次重复。

立方体形状是一种优化:在最大吞吐量(大约为 W_max)一下的时间为浪费。CUBIC 迅速增长以达到 W_max。随着它接近,增长逐渐减弱。相比之下,早期的拥塞控制算法 Reno 具有锯齿波动,并且不像 CUBIC 那样迅速达到最大吞吐量。

cubic regions

现在,在上述 cwnd 图的右侧发生了一些奇怪的事情。我在 CUBIC 放置的拐点(三次函数的平坦部分)下方 W_max 处画了一条红色虚线。CUBIC 的实际内容比 Ha 等人的原始论文中提到的更多。为了解决收敛问题(多个流达到网络资源公平分配的时间)。当看起来吞吐量下降时(W_max_prev > W_max),Linux 将拐点放置在 0.9*W_max。在稳态下,这给 Linux CUBIC 带来了一个周期(在 W_max 和 0.9*W_Max 交替)。

下面可以看到 Linux CUBIC 与 Ha 的原始 CUBIC。Ha 的波形是纯粹的鲨鱼鳍。我们预料到这一点;链路容量只会下降,因此 CUBIC 函数从未有机会通过拐点进入探测阶段。Linux CUBIC’s 的修改将拐点每两个周期放置在 0.9*W_max ,因此即使链路容量没有增加,也会出现 CUBIC 形式。你可以想象这会导致大流量在短时间内人为地处于较低水平,从而允许较小的流获取带宽。你还可以看到在 0.9*W_max 个周期中,流从最大探测区域接近 W_max ,因此可能比如果它是通过 CUBIC 函数的线性中心区域增长时更积极地获取可用带宽。

TCP CUBIC 包含了 2008 年原始 CUBIC 论文的另一个改进。现在 TCP 通常使用 HyStart(Hybird Slow Start)。慢启动往往会严重超过链路容量,造成不必要的拥塞。HyStart 跟踪数据包的时间,寻找拥塞的第一丝迹象,并设置一个水平 sshthresh。此时慢启动有可能严重压垮链路,当超过 sshthresh 时,流切换到拥塞控制(对我们来讲就是 CUBIC)。

cubic linux ha cwnd

关于 RTT 的重要说明:CUBIC 的流量控制独立于 RTT。我听到工程师抱怨 TCP 传输的 RTT 问题,但实际上 CUBIC 是由时间而不是 ACK/RTT 来调节的。仅根据时间调整 cwnd 可以在各种 RTT 下提供更好的性能。

Quic

好吧,很酷。慢启动并不是我的问题所在,流控也不是,ACK-pacing 也不是。尽管 TCP 有像 CUBIC 这样渐进式的改进,但是 TCP 并不总是对大量传输保持一致。

一个新的协议出现了:谷歌的 QUIC。但在你相信 QUIC 将是那个改变一切的卓越新协议之前,我必须说 QUIC 并不是万能的解决方案。它可能对大量传输没有帮助。

谷歌工程师们永无止境地试图证明他们可以重新设计和改进一切,他们创造了一种新的可靠传输++协议, QUIC 。 QUIC 是 TCP+TLS+HTTP/SPDY 的替代品,具有许多改进:更快的握手(尤其是 TLS),无头阻塞的流多路复用,IP 地址迁移的持久性,头部压缩等。 QUIC 的效果是通过解决 TCP+TLS+HTTP/SPDY 中的各种性能问题来加速网页浏览。有人说 QUIC 是 TCP 的继任者,但这并不正确。 QUIC 是 TCP+TLS+SPDY 的演变,将这三个协议合并为一个,并使它们更智能。

对比大量传输,QUIC 相比 TCP 有哪些优势?

  • 重新传输的包会被赋予新的序列号,使得 QUIC 能够区分重传和延迟的包。

  • QUIC 跟踪 RTT 并根据 RTT 时间阈值声明包丢失,而 TCP 则等待重传请求。

  • QUIC 预期会使用 packet pacing(通过间隔包突发来避免缓冲区过载)。

  • 尽管不一定有助于性能,但是 QUIC 可以在 IP 地址更改时保持连接。

有些人会将前向纠错列为 QUIC 的好处,但遗憾的是开源的 QUIC 实现没有实现 FEC,并且看起来 QUIC 的早期版本不太可能具备 FEC。因此 FEC 目前不是一个好处,但将来可能会有。此外,我还省略了 QUIC 的头阻塞改进,因为它们仅与多个流的连接相关。

那么对于大量传输,QUIC 的表现如何?在一些非正式测试中,当 RTT 为 160ms 时,TCP 在丢包值和 RTT 方面都优于 QUIC 的性能。然而,Carlucci 等人发现,当丢包增加时,QUIC 的性能优于 TCP。然而,Carlucci 没有提及他们测试时的 RTT。如果 RTT 是 1ms 或非常小,我会认为他们的结果不代表现实世界,也不具有代表性( QUIC 在低 RTT 时往往表现更好)。所以,根据目前的测试,还不清楚。但正如我之前警告的, QUIC 对于大量传输来说,似乎并没有对 TCP 产生压倒性的改进。

有趣的是,这个对当前 TCP 性能至关重要的组件——即 CUBIC 拥塞控制算法——是 QUIC 的一部分。 CUBIC 目前是 QUIC 默认的拥塞控制算法。虽然很酷,但在我心中也引发了一些疑问,即 QUIC 是否能够同时利用 packet pacing 和 CUBIC 。

在 CUBIC 的领导下,对于大量传输, QUIC 更像是 TCP 的重实现,而不是 TCP 的革命。(公平地说,对于网页/网页应用来说, QUIC 更像是一场革命)。

BBR

幸运的是,还有一种技术可能会同时战胜 QUIC 和 TCP CUBIC : BBR 。

CUBIC 和目前提及到的所有拥塞控制都使用丢包作为拥塞的指标。但还有一种可能更优的方法。那就是监控链路延迟的变化。

查看下面的图。在数据包网络中,当吞吐量低于容量时,路由器缓冲区仅轻度使用,延迟低。但当吞吐量接近容量时,那些缓冲区会填满。缓冲区平滑了流量突增,但也增加了延迟,因为数据包被迫排队。最后,当吞吐量超过容量时,缓冲区被充分利用,瓶颈路由器被迫丢弃数据包。

实际上,你更愿意不依赖丢包作为拥塞指示器。填充缓冲区并造成丢包是低效的操作,会降低链路的有效吞吐量(有用数据传输)。 CUBIC 和其他算法花费时间探索图中的正确部分,这意味着它们正在造成丢包、重传和浪费。在图表中间花费时间,完全避免探索右侧,会更好。这就是基于延迟的拥塞控制和 BBR 的理性所在。

bbr latency buffer.thumbnail

所以 BBR 监控往返时间(RTT),并尝试根据时间检测数据包是否正在缓冲。往返时间增加得越大,数据包缓冲的时间就越长,网络进入拥塞的可能性就越高。从理论上讲,这允许 BBR 更有效地使用队列,并完全避免缓冲区膨胀问题(如果你检测到数据包排队,并回退到排队消失,你不会陷入深度缓冲区膨胀)。

这种方法并非没有问题。另一个基于延迟的算法 Vegas 在与其他算法竞争网络容量时,并没有声称其应有的份额。当一个基于丢包的算法 CUBIC 探索增加容量时, Vegas 检测到网络排队并退避,在 CUBIC 之前退避,留下 CUBIC 偷取一部分其吞吐量。 BBR 似乎与 Vegas 有相反的问题。 APNIC 发现,在 15.2ms 链路上, BBR 制约了 CUBIC,并在 298ms RTT 的互联网测试中略微超过了其应有的份额。

事实上, BBR 似乎与其他具有不同 RTT 的 BBR 流存在共存问题。Shiyao 等人发现,在 100Mbps 链路上,一个 50ms RTT 的 BBR 流会从现有的 10ms RTT BBR 流中窃取 87.3Mbps 的吞吐量。这种不平衡在 10Mbps 到 1Gbps 的网络容量范围内持续存在,50ms 流平均接收了 88.4% 的容量。

抛开问题不谈,谷歌发现 BBR 非常高效。谷歌在其骨干网中使用了 BBR,并看到了 2-20 倍的吞吐量提升。谷歌还声称 BBR 的吞吐量是 CUBIC 的 1/2 到 1/5。在缓冲区膨胀的情况下,他们声称 RTT 的降低更大。

BBR 显然具有优势。同时, BBR 似乎存在一些共存问题。谷歌声称 BBR 的共存问题并不严重,也许互联网的环境使得 BBR 在 YouTube 上的使用比在测试平台上试验时具有更好的共存性。尽管如此, BBR 似乎还需要更多的测试和改进,才能准备好广泛推广。

结尾

所以,这就是全部了。当你的应用商店下载爬虫,你需要给你的巫毒娃娃写上名字时,这个名字不是 ack-pacing ,不是滑动窗口, ,不是流控 ,也不是慢启动。这个名字是 CUBIC 。公平地说, CUBIC 做得很好。 CUBIC 可能是目前最优秀、最稳健的拥塞控制算法。但是,随着互联网带宽的增长和无线网络在链路中的占比越来越大,TCP 的表现并不如人意。自 20 世纪 80 年代互联网拥塞崩溃以来,TCP 已经发生了显著的变化,并且仍在不断发展。 BBR 可能在更缓冲优化的吞吐量下运行 TCP(并给出更好的整体性能)。尽管缺乏改进大块传输的功能, QUIC 可能获得帮助其超越 TCP 的大块传输的改进。

关于您的批量下载,抱歉,在接下来的几年里,我们似乎只能忍受(或者在可能的情况下并行分块下载)。但未来有令人期待的技术。

前方充满激动人心的时刻!

好吧,说这么多,这里有一个总结:

  • 流控、滑动窗口、慢启动和拥塞控制是不同的概念,在 TCP 中扮演不同的角色。

    • 流控会阻止发送方发送超过接收者准备接收的数据量。

    • 流控从接收方直接获取系统状态(接收窗口的大小)。

    • 流控很少限制 TCP 的性能。因为网络吞吐量是瓶颈,而不是网络缓冲区空间。

  • 慢启动只用于启动 TCP 连接。

    • 你的大批量下载主要受 TCP 的拥塞控制算法影响。

    • 大多数 TCP 实现使用 hybrid slow start ,并在连接早期将拥塞控制置于主导地位。这比 80/90 年代的 TCP 更早。

  • 在 20 世纪 80 年代互联网多次崩溃后,拥塞控制和慢启动算法被添加到 TCP 中。

    • 拥塞控制防止发送方导致网络过载。

    • 仅接收间接信息,如丢包、延迟、网络波动(除非使用 ECN )。它必须推断一个安全的值用于拥塞窗口 ( cwnd )。

    • 必须设计成与其他网络流公平共存。

  • CUBIC 是最广泛使用的拥塞控制算法,也是 Linux 上的默认算法。

    • CUBIC 通过快速返回(x^3)到已知容量,利用可用容量,产生类似鲨鱼鳍的图案(与 Reno 不同,它看起来像锯齿形)。

    • CUBIC 不是基于 ACK 调整速度,而是根据时间增加 cwnd 。 CUBIC 在 RTT 中表现良好。

    • CUBIC 包含了增加收敛性的修改,例如当容量似乎在减少时,将立方体中心对齐到 `0.9*W_max 。

  • 在您的系统上调整 TCP 不会帮助批量下载,因为发送方(服务器)负责下游的拥塞控制。

    • 你无法调整服务器,但可以相信 Akamai、Amazon、CloudFlare、Google 的工程师们非常关注性能。

  • QUIC 是一个有潜力的 TCP+TLS+HTTP/SPDY 替代协议

    • QUIC 包含了诸如快速 TLS 握手、无头阻塞的流多路复用、IP 地址变化后的持久性、头部压缩、增量数据包编号用于重传以及基于 RTT 的丢包检测等增强功能。

    • 初始版本的第 0 号似乎不太可能包括第 1 号、数据包速度调节以及高级第 2 号(可能对大量传输有益的改进)。

    • QUIC 使用 CUBIC ,与 TCP 相同的拥塞控制算法!

    • QUIC ,目前来看,与 TCP 相比,在大量传输方面不太可能提供更好的性能。(到目前为止,基准测试证实了这一点)。

  • BBR 是一种有前途的新拥塞控制算法。

    • 与 CUBIC 和大多数其他拥塞控制算法不同, BBR 是基于延迟的,并使用数据包定速。

    • 尝试将传输速率保持在网络缓冲区开始排队数据包之前的水平。

    • 与基于丢包的拥塞控制不同,它在过大的缓冲区/缓冲区膨胀方面表现良好。

    • 谷歌声称使用 BBR 可以显著提高性能,但外部测试显示在异构网络和与 CUBIC 共存时存在问

Last moify: 2022-12-04 15:11:33
Build time:2025-07-18 09:41:42
Powered By asphinx