译自: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:一种新的网络传输协议。
TCP CUBIC 流样本。在橙色点上,CUBIC 达到峰值并能够增长 cwnd,灰色点表示 Linux CUBIC 将 cwnd 收敛到 0*9*W_max(尽管容量下降,但是依然表示出 CUBIC 形状)
首先,关于调整的问题,你很可能不需要调整你系统的 TCP 参数。我能想到的只有两种可能需要调整的场景:
你是一个数据中新,并希望为低延迟网络采用
Data Center TCP
。你是用 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 吧。(当然也要赞美它,它真的很酷!)。
如上图所示,我们的 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 那样迅速达到最大吞吐量。
现在,在上述 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)。
关于 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 监控往返时间(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 共存时存在问