大家好,我是小林。很早之前跟大家图解过TCP的拥塞控制:你还在为TCP重传、滑动窗口、流量控制、拥塞控制发愁吗?看完图解就不愁了但是没有提及到谷歌的BBR拥塞控制算法,我们之前讲的都是传统的拥塞控制算法,传统的拥塞控制是基于丢包反馈的方式来做控制的,而谷歌的BBR 拥塞控制算法就...
大家好,我是小林。
很早之前跟大家图解过 TCP 的拥塞控制:你还在为 TCP 重传、滑动窗口、流量控制、拥塞控制发愁吗?看完图解就不愁了
但是没有提及到谷歌的 BBR 拥塞控制算法,我们之前讲的都是传统的拥塞控制算法,传统的拥塞控制是基于丢包反馈的方式来做控制的,而谷歌的 BBR 拥塞控制算法就比较牛逼,是基于时延检测的方式来做控制的,也就是当网络延迟到一定程度后,就会自动减少拥塞窗口,避免造成丢包,因为丢包后,就要从慢启动开始了,这样 TCP 的传输速率会瞬间下降的。
今天,我们再来深入谈一谈 TCP 拥塞控制,还有谷歌的 BBR 算法,比较硬核,文章共 1W 字。
发车啦!
互联网协议套件是一个网络通信模型以及整个网络传输协议家族,由于该协议簇包含两个核心协议:TCP(传输控制协议)和IP(网际协议),因此常被通称为TCP/IP协议族。
TCP/IP协议对于数据应该如何封装、定址、传输、路由以及在目的地如何接收等基本过程都加以标准化。它将通信过程抽象化为四个层次,并采取协议堆栈的方式分别实现出不同通信协议,实际使用的四层结构是七层OSI模型的简化。我们可以看到TCP/IP协议栈是一个简化的分层模型,是互联网世界连接一切的基石,一起来看一张七层模型vs四层模型的简图:
大约在1988年之前TCP/IP是没有拥塞控制的,但是随着网络接入规模的发展之前仅有的端到端窗口控制已经无法满足要求,在1986年引发大规模网络瘫痪,此时就要提到一个重量级人物:Van Jacobson范·雅各布森。
这位力挽狂澜的人物入选了计算机名人堂Internet Hall of Fame,Van Jacobson大神提出并设计实施了TCP/IP拥塞控制,解决了当时最大的问题,来简单看下Van Jacobson的维基百科简介(笔者做了部分删减):
范·雅各布森Van Jacobson是目前作为互联网技术基础的TCP/IP协议栈的主要起草者,他以其在网络性能的提升和优化的开创性成就而闻名。2006年8月,他加入了帕洛阿尔托研究中心担任研究员,并在位于相邻的施乐建筑群的Packet Design公司担任首席科学家。在此之前,他曾是思科系统公司首席科学家,并在位于劳伦斯伯克利国家实验室的网络研究小组任领导者。
范·雅各布森因为在提高IP网络性能提升和优化所作的工作而为人们所知,1988到1989年间,他重新设计了TCP/IP的流控制算法(Jacobson算法),他因设计了RFC 1144中的TCP/IP头压缩协议即范·雅各布森TCP/IP头压缩协议而广为人知。此外他也曾与他人合作设计了一些被广泛使用的网络诊断工具,如traceroute,pathchar以及tcpdump 。如图为Van Jacobson计算机名人堂的简介:范·雅各布森于2012年4月入选第一批计算机名人堂,计算机名人堂简介:https://www.internethalloffame.org/inductees/van-jacobson
笔者找了Van Jacobson和Michael J. Karels在1988年11月发布的关于拥塞避免和控制的论文,总计25页,感兴趣的读者可以查阅:
https://ee.lbl.gov/papers/congavoid.pdf我们常用的tracetoute和tcpdump也是van-jacobson大神的杰作,作为互联网时代的受益者不由得对这些互联网发展早期做出巨大贡献的开拓者、创新者、变革者心生赞叹和敬意。
维基百科对于流量控制Flow Control的说明:
In data communications, flow control is the process of managing the rate of data transmission between two nodes to prevent a fast sender from overwhelming a slow receiver.
It provides a mechanism for the receiver to control the transmission speed, so that the receiving node is not overwhelmed with data from transmitting node.翻译一下:
在数据通信中,流量控制是管理两个节点之间数据传输速率的过程,以防止快速发送方压倒慢速接收方。
它为接收机提供了一种控制传输速度的机制,这样接收节点就不会被来自发送节点的数据淹没。可以看到流量控制是通信双方之间约定数据量的一种机制,具体来说是借助于TCP协议的确认ACK机制和窗口协议来完成的。
窗口分为固定窗口和可变窗口,可变窗口也就是滑动窗口,简单来说就是通信双方根据接收方的接收情况动态告诉发送端可以发送的数据量,从而实现发送方和接收方的数据收发能力匹配。
这个过程非常容易捕捉,使用wireshark在电脑上抓或者tcpdump在服务器上抓都可以看到,大白在自己电脑上用wireshark抓了一条:
我们以两个主机交互来简单理解流量控制过程:
接收方回复报文头部解释:
图中RcvBuffer是接收区总大小,buffered data是当前已经占用的数据,而free buffer space是当前剩余的空间,rwnd的就是free buffer space区域的字节数。
HostB把当前的rwnd值放入报文头部的接收窗口receive window字段中,以此通知HostA自己还有多少可用空间, 而HostA则将未确认的数据量控制在rwnd值的范围内,从而避免HostB的接收缓存溢出。可见流量控制是端到端微观层面的数据策略,双方在数据通信的过程中并不关心链路带宽情况,只关心通信双方的接收发送缓冲区的空间大小,可以说是个速率流量匹配策略。
流量控制就像现实生活中物流领域中A和B两个仓库,A往B运送货物时只关心仓库B的剩余空间来调整自己的发货量,而不关心高速是否拥堵。
我们还需要一个宏观层面的控去避免网络链路的拥堵,否则再好的端到端流量控制算法也面临丢包、乱序、重传问题,只能造成恶性循环。
我们从一个更高的角度去看大量TCP连接复用网络链路的通信过程:
所以拥塞控制和每一条端到端的连接关系非常大,这就是流量控制和拥塞控制的深层次联系,所谓每一条连接都顺畅那么整个复杂的网络链路也很大程度是通畅的。
在展开拥塞控制之前我们先考虑几个问题:
TCP拥塞控制算法的目的可以简单概括为:公平竞争、充分利用网络带宽、降低网络延时、优化用户体验,然而就目前而言要实现这些目标就难免有权衡和取舍。
在理解拥塞控制算法之前我们需要明确一个核心的思想:闻道有先后 术业有专攻,笔者觉得这是一个非常重要的共识问题,把A踩在泥土里,把B吹捧到天上去,都不是很好的做法。
实际的网络环境十分复杂并且变化很快,并没有哪个拥塞控制算法可以全部搞定,每一种算法都有自己的特定和适用领域,每种算法都是对几个关键点的权衡,在无法兼得的条件下有的算法选择带宽利用率,有的算法选择通信延时等等。
在明确这个共识问题之后,我们对待各个拥塞控制算法的态度要平和一些,不要偏激地认为谁就是最好,几十年前的网络状况和现在是截然不同的,我们永远都是站在巨人的肩膀之上的,这也是科学和文明进步的推动力。
传统拥塞控制算法并不是一蹴而就的,复杂的网络环境和用户的高要求推动着拥塞控制算法的优化和迭代,我们看下基于丢包策略的传统拥塞控制算法的几个迭代版本,如图所示:
与此同时还有一类算法是基于RTT延时策略来进行控制的,但是这类算法在发包速率上可能不够激进,竞争性能不如其他算法,因此在共享网络带宽时有失公平性,但是算法速率曲线却是很平滑,我们暂且把这类算法当做君子吧!
其中比较有名的Vegas算法是大约在1995年由亚利桑那大学的研究人员拉里·彼得森和劳伦斯·布拉科夫提出,这个新的TCP拥塞算法以内华达州最大的城市拉斯维加斯命名,后成为TCP Vegas算法。
关于基于RTT的TCP Vegas算法的详细介绍可以查阅文档:
http://www.cs.cmu.edu/~srini/15-744/F02/readings/BP95.pdf文档对Vegas算法和New Reno做了一些对比,我们从直观图形上可以看到Vegas算法更加平滑,相反New Reno则表现除了较大的波动呈锯齿状,如图所示:
实际上还有更细粒度的分类,由于不是今天的重点,就不再深入展开了,当前使用的拥塞控制算法还是基于丢包Loss-Based作为主流。
Congestion Window (cwnd) is a TCP state variable that limits the amount of data the TCP can send into the network before receiving an ACK.
The Receiver Window (rwnd) is a variable that advertises the amount of data that the destination side can receive.
Together, the two variables are used to regulate data flow in TCP connections, minimize congestion, and improve network performance.笔者在rfc5681文档中也看到cwnd的定义:
这个解释指出了cwnd是在发送方维护的,cwnd和rwnd并不冲突,发送方需要结合rwnd和cwnd两个变量来发送数据,如图所示:
cwnd的大小和MSS最大数据段有直接关系,MSS是TCP报文段中的数据字段的最大长度,即MSS=TCP报文段长度-TCP首部长度。
注:有的版本的TCP算法不一定没有快速恢复阶段
如图为典型的包含4个策略的拥塞控制:
如图为发生超时重传RTO时的过程:
TCP 算法名的命名方式最早出现在Kevin Fall和Sally Floyd1996年发布的论文中。
这两个算法代号取自太浩湖Lake Tahoe和里诺市,两者算法大致一致,对于丢包事件判断都是以重传超时retransmission timeout和重复确认为条件,但是对于重复确认的处理两者有所不同,对于超时重传RTO情况两个算法都是将拥塞窗口降为1个MSS,然后进入慢启动阶段。
TCP Tahoe算法:如果收到三次重复确认即第四次收到相同确认号的分段确认,并且分段对应包无负载分段和无改变接收窗口的话,Tahoe算法则进入快速重传,将慢启动阈值改为当前拥塞窗口的一半,将拥塞窗口降为1个MSS,并重新进入慢启动阶段。
TCP Reno算法:如果收到三次重复确认,Reno算法则进入快速重传只将拥塞窗口减半来跳过慢启动阶段,将慢启动阈值设为当前新的拥塞窗口值,进入一个称为快速恢复的新设计阶段。
TCP New Reno是对TCP Reno中快速恢复阶段的重传进行改善的一种改进算法,New Reno在低错误率时运行效率和选择确认SACK相当,在高错误率仍优于Reno。
TCP BIC旨在优化高速高延迟网络的拥塞控制,其拥塞窗口算法使用二分搜索算法尝试找到能长时间保持拥塞窗口最大值,Linux内核在2.6.8至2.6.18使用该算法作为默认TCP拥塞算法。
CUBIC则是比BIC更温和和系统化的分支版本,其使用三次函数代替二分算法作为其拥塞窗口算法,并且使用函数拐点作为拥塞窗口的设置值,Linux内核在2.6.19后使用该算法作为默认TCP拥塞算法。
TCP PRR是旨在恢复期间提高发送数据的准确性,该算法确保恢复后的拥塞窗口大小尽可能接近慢启动阈值。在Google进行的测试中,能将平均延迟降低3~10%恢复超时减少5%,PRR算法后作为Linux内核3.2版本默认拥塞算法。
TCP BBR是由Google设计于2016年发布的拥塞算法,该算法认为随着网络接口控制器逐渐进入千兆速度时,分组丢失不应该被认为是识别拥塞的主要决定因素,所以基于模型的拥塞控制算法能有更高的吞吐量和更低的延迟,可以用BBR来替代其他流行的拥塞算法。
Google在YouTube上应用该算法,将全球平均的YouTube网络吞吐量提高了4%,BBR之后移植入Linux内核4.9版本。
之后cwnd不再呈指数增长从而进入拥塞避免阶段(注cwnd增长的单位是MSS),当然如果在慢启动阶段还未到达阈值ssthresh而出现丢包时进入快速重传等阶段,需要注意的是如果网络状况良好RTT时间很短,那么慢启动阶段将很快到达一个比较高的发送速率,所以将慢启动理解为试探启动更形象。
本次发送丢包时仍然会调整ssthresh的值,具体拥塞避免增长过程:发送方每收到一个ACK数据包时将cwnd=cwnd 1/cwnd,每经过一个RTT将cwnd自增1。
RTO是随着复杂网络环境而动态变化的,在拥塞控制中发生超时重传将会极大拉低cwnd,如果网络状况并没有那么多糟糕,偶尔出现网络抖动造成丢包或者阻塞也非常常见,因此触发的慢启动将降低通信性能,故出现了快速重传机制。
所谓快速重传时相比超时重传而言的,重发等待时间会降低并且后续尽量避免慢启动,来保证性能损失在最小的程度,如图所示:
快速重传和超时重传的区别在于cwnd在发生拥塞时的取值,超时重传会将cwnd修改为最初的值,也就是慢启动的值,快速重传将cwnd减半,二者都将ssthresh设置为cwnd的一半。
从二者的区别可以看到,快速重传更加主动,有利于保证链路的传输性能,但是有研究表明3个ACK的机制同样存在问题,本文就不做深入阐述了,感兴趣的读者可以自主查阅。
快速重传是基于对网络状况没有那么糟糕的假设,因此在实际网络确实还算好的时候,快速重传还是很有用的,在很差的网络环境很多算法都很难保证效率的。
如果连接端都无所顾忌地发生数据包,那么网络链路很快就到了瓶颈了,数据通信完全无法保障,所以要到达一个稳定高效的网络环境还是需要费很大心思的,这其中有两个重要的概念:公平性和收敛性。
说来惭愧笔者在网络上找了很多资料去理解TCP拥塞控制的公平性和收敛性,但是仍然没有获得一个很好的权威解释,所以只能结合一些资料和自身的理解去阐述所谓的公平性和收敛性。
笔者认为公平性是相对于网络链路中的所有连接而言的,这些共享链路的连接启动和结束的时间不同,在实际的交互过程中每条连接占有带宽的机会是均等的,并且由于带宽限制连接双方通信的数据量是动态调整并且近似收敛于某个值,也就是呈现一个锯齿状或者更加平滑的波动曲线,对于基于丢包的拥塞控制算法而言AIMD线性增乘性减策略起了关键控制作用。
接下来我们来重点看下AIMD特性,先来贴一张经典的图,直观看AIMD的过程:
看看维基百科对于AIMD的定义:
The additive-increase/multiplicative-decrease(AIMD) algorithm is a feedback control algorithm best known for its use in TCP congestion control.
AIMD combines linear growth of the congestion window with an exponential reduction when congestion is detected.
Multiple flows using AIMD congestion control will eventually converge to use equal amounts of a shared link.
The related schemes of multiplicative-increase/multiplicative-decrease (MIMD) and additive-increase/additive-decrease (AIAD) do not reach stability.简单翻译一下:线性增加乘性减少算法是一个反馈控制算法,因其在TCP拥塞控制中的使用而广为人知,AIMD将线性增加拥塞窗口和拥塞时乘性减少窗口相结合,基于AIMD的多个连接理想状态下会达到最终收敛,共享相同数量的网络带宽,与其相关的乘性增乘性减MIMD策略和增性加增性减少AIAD都无法保证稳定性。
AIMD相比MIMD和AIAD在连接进入拥塞避免阶段使用试探线性加策略而不是乘性加策略更加安全,在探测丢包时则大幅度乘性减少到1/2这样对于缓解拥塞会有比较好的效果更加快速,相反如果探测到丢包时采用线性减少AD可能拥塞持续的时间会更长,总体来说AIMD算是一个比较简单实用的工程版本的反馈控制,也具备可工程收敛性,因而被广泛实用。
时间拉回20多年前,在互联网早期几乎所有的设备都是通过有线网络进行连接通信的,这也是拥塞控制在设计之后一直都起到不错作用的重要因素,有线连接的网络稳定性比较好,因此把丢包作为网络拥堵的一个特征也很正常。
再拉回到现在,从2010年之后移动互联网蓬勃发展,移动终端的持有量已经可以称为海量,无线网络的引入让网络环境变得更加复杂,因此不稳定丢包变得更加频繁,但是这时的丢包就不一定是网络拥堵造成的了,因为整个数据包经过多重路由、交换机、基站等基础通信设备每个环节都可能发生异常。
在弱网环境下,尤其是移动互联网中之前的基于AIMD的拥塞控制策略可能会由于丢包的出现而大幅降低网络吞吐量,从而对网络带宽的利用率也大大下降,这时我们采用更加激进的控制策略,或许可以获得更好的效果和用户体验。
恶意丢包的情况下,基于AIMD的拥塞控制确实就相当于被限速了,因为AIMD确实有些保守谨慎了,这个其实也很好理解的哈。
我们都知道在移动网络环境下是由终端以无线形式和附近的基站交互数据,之后数据传输至核心网,最后落到具体的服务器所在的有线网络,其中最后一公里的区域属于高延时场景,有线网络属于低延时高带宽场景。
在国外有相关实验证明弱网环境下RTT的变化对于使用传统拥塞控制算法下网络吞吐量的影响,数据和曲线如图所示:
实验含义:RTT的增大影响了比如CUBIC这类拥塞控制算法的慢启动等阶段,我们知道慢启动阶段每经过1个RTT周期拥塞窗口cwnd将加倍,但是更大的RTT就意味着发送方以很低的速率发送数据,更多的时间是空闲的,发包的加速度极大将低了,所以整个吞吐量就下降很明显。
看下实验者的原文表述:
The delay before acknowledgment packets are received (= latency) will have an impact on how fast the TCP congestion window increases (hence the throughput).When latency is high, it means that the sender spends more time idle (not sending any new packets), which reduces how fast throughput grows.
看下维基百科对BBR算法的说明和资料:
相关文献:https://queue.acm.org/detail.cfm?id=3022184
TCP BBR(Bottleneck Bandwidth and Round-trip propagation time)是由Google设计,并于2016年发布的拥塞算法,以往大部分拥塞算法是基于丢包来作为降低传输速率的信号,而BBR基于模型主动探测。
该算法使用网络最近出站数据分组当时的最大带宽和往返时间来创建网络的显式模型。数据包传输的每个累积或选择性确认用于生成记录在数据包传输过程和确认返回期间的时间内所传送数据量的采样率。
该算法认为随着网络接口控制器逐渐进入千兆速度时,分组丢失不应该被认为是识别拥塞的主要决定因素,所以基于模型的拥塞控制算法能有更高的吞吐量和更低的延迟,可以用BBR来替代其他流行的拥塞算法例如CUBIC。Google在YouTube上应用该算法,将全球平均的YouTube网络吞吐量提高了4%,在一些国家超过了14%。BBR之后移植入Linux内核4.9版本,并且对于QUIC可用。
TCP Westwood改良自New Reno,不同于以往其他拥塞控制算法使用丢失来测量,其通过对确认包测量来确定一个合适的发送速度,并以此调整拥塞窗口和慢启动阈值。其改良了慢启动阶段算法为敏捷探测和设计了一种持续探测拥塞窗口的方法来控制进入敏捷探测,使链接尽可能地使用更多的带宽。TCP WestWood算法也是基于带宽和延时乘积进行设计的,但是带宽和延时两个指标无法同时测量,因为这两个值是有些矛盾的极值,要测量最大带宽就要发送最大的数据量但是此时的RTT可能会很大,如果要测量最小的RTT那么久意味着数据量非常少最大带宽就无法获得。
TCP BBR算法采用交替采样测量两个指标,取一段时间内的带宽极大值和延时极小值作为估计值,具体的实现本文就不展开了。
1.曲线图示说明:
两个纵轴从上到下分别为RTT和发送速率,并且整个过程分为了3个阶段:应用限制阶段、带宽限制阶段、缓冲区限制阶段。
2.曲线过程说明:
网上有一些资料都提及到了这张图,其中的一些解释也并不算非常清晰,结合这些资料和自己的认识,笔者认为在网络链路的缓存区没有被使用时RTT为最小延时MinRTT,在网络链路缓冲区被占满时出现最大带宽MaxBW(链路带宽 链路缓存),但是此时的MaxBW和MinRTT并不是最优的而是水位比较高的水平,有数据表明按照2ln2的增益计算此时为3BDP,整个过程中MinRTT和MaxBW是分开探测的,因为这二者是不能同时被测量的。
To handle Internet link bandwidths spanning 12 orders of magnitude, Startup implements a binary search for BtlBw by using a gain of 2/ln2 to double the sending rate while delivery rate is increasing. This discovers BtlBw in log2BDP RTTs but creates up to 2BDP excess queue in the process.
曲线说明:这两个坐标给出了10Mbps和40msRTT的网络环境下CUBIC和BBR的一个对比过程,在上面的图中蓝色表示接收者,红色表示CUBIC,绿色表示BBR,在下面的图中给出了对应上图过程中的RTT波动情况,红色代表CUBIC,绿色代表BBR。
从图中我们可以看到在YouTube应用BBR算法之后,就吞吐量普遍有4%左右的提升,特别地在日本的提升达到14%,RTT的下降更为明显平均降低33%,其中IN(印度地区)达到50%以上,在丢包率测试中BBR并不想CUBIC那么敏感,在丢包率达到5%是吞吐量才开始明显下降。