艾巴生活网

您现在的位置是:主页>科技 >内容

科技

深度解析TCP(BBR技术)

2023-12-17 11:34:23科技帅气的蚂蚁
今天,我推荐一篇对TCP BBR技术进行了深入分析的文章。希望你能学到一些真正的知识,了解背后的设计原理,这样才能迎接各种面试和工作挑战

深度解析TCP(BBR技术)

今天,我推荐一篇对TCP BBR技术进行了深入分析的文章。希望你能学到一些真正的知识,了解背后的设计原理,这样才能迎接各种面试和工作挑战!

宏观背景下的BBR

80年代拥塞的崩溃导致了80年代拥塞控制机制的出现。从某种意义上说,这是一种速战速决的策略。针对80年代的拥塞,提出了80年代的拥塞控制算法,即ss、ssthresh和拥塞避免。

说实话,这些机制完美的适应了80年代的网络特点,低带宽,浅缓存队列,它们的美好一直持续到了2000年代。

然后互联网爆炸,多媒体应用,尤其是图片、音文章应用,使得带宽急剧增加。摩尔美国定律使得存储设备趋于廉价,路由器队列缓存急剧增加,这是BBR诞生的背景。换句话说,80年代的CC已经不适用了,2010年代还需要一个。

如果说上世纪80年代的CC旨在趋同,那么这一次BBR的目标是效率最大化。e,至少我个人是这么认为的,这也符合BBR提高带宽利用率的初衷!

插入图像gif:

开始发短信

国庆前看了bbr算法,发现这是唯一正确的路(可能有点夸张,但至少是一个通往正确道路的起点!),所以我花了一些时间研究它,包括它的补丁注释,补丁代码,还亲自把bbr补丁移植到了内核的较低版本。在这个过程中,我也产生了一些想法,整理了一篇文章作为备忘录,写下来如下。多年以后,我再看TCP bbr算法的数据,我的记录可以算是华人社区少有的吃螃蟹第一记录,这就足够了!

正文之前,给出这篇文章的图例:

BBR的构成

bbr算法实际上非常简单,在实现中由五部分组成:

BBR的构成

1.即时汇率的计算

计算一个瞬间带宽bw,这是bbr所有计算的基准。bbr将根据当前瞬时带宽及其管道状态计算起搏速率和cwnd(见下文)。后面我们会看到,这种即时带宽计算方法的突破性改进是bbr简单高效的根源。该方案基于标量计算,不再关注数据的含义。在bbr运行过程中,系统将跟踪到目前为止的最大瞬时带宽。

2.跟踪2。循环时间(Round-Trip Time)

bbr之所以能够获得非常高的带宽利用率,是因为它能够安全大胆地检测出最大带宽和最小rtt,使得计算出的BDP是目前为止TCP流水线的最大容量。Bbr 的目标是达到这个最大容量!这个目标最终推动了cwnd的计算。在bbr运行期间,系统将追踪到目前为止的最小RTT。

3.维护3。BBR国家机器

根据互联网的拥塞行为,bbr算法定义了四种状态,即启动、耗尽、探测_带宽和探测_RTT。Bbr可以通过连续观察上面计算的瞬时带宽bw和rtt,在这四种状态之间自由切换。与以往所有的拥塞控制算法相比,其革命性的改进在于bbr拥塞算法不再跟踪系统的TCP拥塞状态机,而是以统一的方式处理调步率和cwnd的计算,无论TCP是处于开放状态还是无序状态,或者已经处于恢复状态。换句话说,bbr算法可以感觉不到丢包,只能看到bw和rtt。

4.结果输出-起搏率和cwnd

首先必须说,bbr的输出不仅仅是一个cwnd,更重要的是起搏率。传统上,cwnd是TCP拥塞控制算法的唯一输出,但它只规定了当前TCP可以发送多少数据,并没有我不知道如何发送这么多数据。在Linux的实现中,发送这么多数据怎么办?简单粗暴,突然!Linux在忽略接收方公告窗口的前提下,会将cwnd窗口中的数据全部突发出来,这往往会导致路由器排队。在深度排队的情况下,会测到rtt剧烈抖动。

Bbr不仅计算cwnd,还计算相应的调步速率,该调步速率指定由cwnd指示的数据窗口的分组之间的时间间隔。

5.使用其他外部机制-fq、rack等。

bbr之所以能高效运行,而且这么简单,是因为很多机制不是自己实现的,而是利用外部已有的机制实现的。例如,下一节将解释为什么在计算带宽bw时它可以安全地计算重传数据。

带宽计算细节和状态机

1.瞬时带宽的计算

Bbr作为一种纯粹的拥塞控制算法,完全忽略了系统层面的TCP状态。计算带宽时只需要两个值:

1)。回答了多少数据,记录为已送达;

2)。1)响应中传递这么多数据所用的时间记录为interval_us。

将以上两者相除得到带宽:

bw=已交付/时间间隔_美国

很简单!以上计算都是标量计算,只关注数据的大小,不关注数据的意义。例如,在已交付的集合中,bbr不我不关心某个响应是通过重传后的ACK、正常ACK还是SACK来确认的。Bbr只关心有多少人回答!

这与TCP/IP网络模型是一致的,因为在中间链路上,路由器和交换机不我不在乎这些包是重发还是乱序。但是,这些地方也会出现拥堵。既然拥堵点不不关心数据的意义,TCP为什么要关心?反之,让我们来看看拥塞的原因,即数据量超过了路由器的带宽限制。要利用这一点,我们只需要小心控制发送的数据量,不考虑任何乱序、重传等等。当然,我的意思是,拥塞控制算法不会我不必管这些,但这并不重要。这并不意味着他们被抛弃了。其他机制会关注他们,比如SACK机制,RACK机制,RTO机制等。

接下来,让让我们看看delivered和interval_us的集合是如何实现的。像往常一样,我我不打算分析源代码,因为如果我分析源代码,它往往很难抓住关键点,而我可以过了一段时间我自己也不明白。相反,如果我画一幅画,我可以过滤掉许多不正常的流如不太可能或不目前不需要注意:

上图中,我故意用了一个极端的例子。在这个例子中,我几乎总是使用SACK。当X被SACK时,我们可以很容易地根据图表计算出从发送的数据包为7的时间到X被确认的时间。一共确认了12-7=5个包,也就是这段时间网络上有5个包是空的!我们可以很容易地计算出带宽值。我的这张图除了说明带宽计算方法,还有一个目的,就是说明bbr在计算带宽时并不关注数据包是否按顺序确认,而只关注数量,即网络清除的数据包数量。真正的计算,唐don 这些事情,你不能乱猜不要再猜了!

计算出的bw是之后所有bbr计算的基础。

2.状态机

Bbr 的状态机转换图和注释如下图所示:

通过上面的状态机和上一节的带宽计算方法,我们知道bbr是如何工作的:基于当前带宽和当前增益系数连续计算调步速率和cwnd,并将这两个结果作为拥塞控制算法的输出。在TCP连接的连续过程中,每收到一个ACK,都会计算瞬时带宽,然后将结果反馈给bbr 管道状态机。bbr的宗旨就是不断调整增益系数。我们发现它是一个典型的封闭反馈系统,与TCP目前处于什么拥塞状态无关。其示意图如下:

这与以前所有的拥塞控制算法有很大的不同。在以前的算法中,我们发现拥塞算法的内部拥塞受外部拥塞状态的影响。例如,在恢复状态下,它甚至不会进入拥塞控制算法。在bbr进入内核之前,Linux使用PRR算法控制恢复状态的窗口调整。即使此时网络已经恢复,TCP也找不到它,因为TCP的恢复状态还没有恢复到Open。这才是根本原因!

Pacirate和cwnd计算

这一节看似是重点中的重点,但我觉得如果理解bbr的带宽计算、状态机、增益系数等概念,在这里就不是重点,只是一个公式化的结论。

如何计算Pacirate?很简单,就是使用时间窗内的最大BW(默认10轮采样)。最后一个样本的瞬时带宽,如果可能,用于更新时间窗口内设置的带宽采样值。能不能按照这个时间窗内的最大BW发送数据?看增益系数的当前值,设为G,那么BW*G就是起搏率的值,isn 这不是很简单吗?

至于cwnd的计算,可能会复杂一点,但也是可以理解的。我们知道,cwnd其实描述的是一个网络管道(rwnd描述的是接收端的缓冲区),所以cwnd其实就是这个管道的容量,也就是BDP!

我们已经有了BW,但是缺少的是D,也就是RTT。但是唐别忘了,bbr一直在收集最小的RTT值。注意bbr不不要使用任何移动平均算法来猜一猜RTT(我之所以用猜测代替预测,是因为猜测的结果往往更不可靠!),而是直接冒泡收集最小的RTT(注意这个RTT是TCP系统级的移动指数平均的结果,也就是s RTT,但是brr不会再平均这个结果了!)。我们要用这个最小RTT做什么?

目前BDP算!bbr在这里取的RTT是这个最小RTT。最小的RTT代表了最好的RTT。既然已经达到,说明是可以再次达到的客观RTT,有利于网络管道利用率的最大化!

我们用BDP * G 计算cwnd,其中G 是cwnd的增益系数,与带宽增益系数含义相同,是根据bbr的状态机得到的!

关于BBR的细节

这部分的标题比较奇怪。既然是细节,为什么要简述?

它这是我的风格。一方面,它因为很少有人真正注意到这些事情。另一方面,它因为我通常不肤浅。不要分析代码和代码中的每一个异常流程。我不我不认为那些东西对理解原理有很大帮助,那些东西只在研发和优化过程中有用。所以,像往常一样,我我将谈论一些详细信息一如既往。

1.大胆大胆的安全检测

看了bbr,我觉得以前的TCP拥塞控制算法都是错的。它这不是错误的想法,而是实施的问题。

bbr之所以敢大胆探索预估带宽,是因为TCP给了它更大的动力!在bbr之前,很多应该由拥塞控制算法处理的细节都没有被拥塞控制算法管理。在详述之前,我们必须区分两件事:

1)。传输多少数据?

2)。传输哪些数据?

根据上帝生意就是上帝的生意和凯撒的业务是凯撒的业务,这两件事应该是通过不同的机制完成的。在不考虑对等接收窗口的情况下,拥塞窗口是唯一的主导因素。的问题要传输多少数据应该由拥塞算法来回答,而传输哪些数据应该由TCP拥塞状态机和SACK分配来决定。诚然,这两个问题是不同的,不应该混为一谈。

但是在bbr进入内核之前的Linux TCP实现中,上述两个问题并不是特别清楚。TCP的拥塞状态只有在开放时才是上述责任分离的完美呈现。一旦进入丢失或恢复状态,拥塞控制算法可以不要对问题1):传输多少数据。在现有的Linux实现中,PRR算法将接管一切,并不断将窗口降低到ssthresh。在迷失状态下,反应会更加激烈,cwnd会硬着陆!然后,在执行慢启动之前,等待丢失的数据传输成功。在重新进入开放状态之前,拥塞控制算法几乎不起作用。这不是高速公路模式(小颠簸,拍照后停在路边,自行解决),更像是闹市区的交通事故处理方式(无论如何,保留现场,直到交警和保险公司的人来现场处置)。

Bbr算法躲过了所有这些错误的做法。在bbr s补丁,它不仅仅是完成了一个tcp_bbr.c,而是对整个tcp拥塞控制框架进行了一次大的操作。我们可以从以下拥塞控制核心功能中看出这一点:

static void TCP _ cong _ control(struct sock * sk,u32 ack,u32 acked_sacked,int flag,const struct rate _ sample * RS){ const struct inet _ connection _ sock * icsk=inet _ csk(sk);如果(ICSK-ICSK _加州_ OPS-CONG _ CONTROL){//如果是bbr,就完全被bbr接管了,不管现在是什么状态!/*目前只有bbr使用过这种机制,但相信在不久的将来,*会有越来越多的拥塞控制算法使用这种统一的完全接管机制!*个人几个月前写了一个补丁,接手了tcp_cwnd_reduction *这个prr的降窗过程。如果当时有这个框架,我就有福了!*/icsk-》icsk _ ca _ ops-》cong _ control(sk,RS);返回;}//否则继续之前的错误方法!if(TCP _ in _ cwnd _ reduction(SK)){/非开放状态的拥塞算法不接受窗口调整tcp_cwnd_reduction(sk,acknowledged _ packed,flag);} else if (tcp_may_raise_cwnd(sk,flag)){TCP _ cong _ avoid(sk,ack,acked _ sacked);} TCP _ update _ pacing _ rate(sk);}

在这个框架下,无论处于哪种状态(开放、无序、恢复、迷失。),如果拥塞控制算法声明自己有这个能力,那么能传输多少数据完全由拥塞控制算法自己决定,TCP拥塞状态控制机制不再干涉!

2.为什么bbr可以忽略恢复和丢失状态?

看了上面第一点,就好理解了。

在第1点中,我描述了bbr确实忽略了恢复等非开放拥塞,但为什么可以忽略?一般来说,很多人会质疑,说bbr采取这样不计后果的方式,最终会让车窗卡住,停止滑动。但我必须反驳它,唐你不知道cwnd只是一个标量吗?我我将画一张图来分析:

你明白吗?没有问题!基本上,当我们讨论拥塞控制算法时,我们会忽略流量控制,因为我们不我不希望rwnd和cwnd混合,但是在这里,它们相遇了。幸运的是,它没有不要引起冲突!

然而,这并不是故事的全部。本节旨在简要分析,所以我们赢了不要注意代码处理的细节。在bbr的实现中,如果算法外的TCP拥塞状态已经进入Lost,那么cwnd是什么?在bbr之前的所有拥塞算法中,包括cubic,当TCP核心实现是从调整cwnd到1或者prr到ssthresh,直到恢复到开放状态,拥塞算法无权干涉这个过程,但是bbr没有。虽然据说cwnd进入迷失状态后会硬着陆到1,但是由于bbr s接管,cwnd仍然可以根据丢失期间的即时带宽进行调整!

这是什么意思?

这意味着bbr可以区分噪声丢包和拥塞丢包!

答.噪声包丢失

如果是噪声丢包,接收到重排序重复ACK后,bbr不不能区分确认是由ACK还是SACK引起的,所以在bbr s看来,瞬时带宽并没有减少,可能还增加了,所以一个数据包的丢失并不会造成什么。bbr还是会给更大的cwnd额度。此时,虽然TCP可能已经进入恢复状态,但是,bbr仍然根据其bw和调整后的增益系数计算cwnd的新值,不会受到任何TCP拥塞的影响。

这样就区分了所有的噪声包丢失!Bbr 的宗旨是:首先,在我的bw计算表明我拥塞之前,任何传统的TCP拥塞判断——丢包/时延增加,都会失效。我不我不在乎分组丢失和RTT增加。"然后brr会说,但我在意的是RTT没有达到我一段时间收集的最小值或者更小(随便你,但我个人更喜欢自学)。这可能意味着链路真的很拥堵!"

乙)的.拥塞丢包

把a)的讨论变成反面,我们会得到一个精彩的封闭结论。这样bbr不仅消除了锯齿状的吞吐量曲线(ssthresh),而且bbr不使用ssthresh!),而且还消除了传统拥塞控制算法(bbr和封闭傻Appex之前)的判断滞后问题。当cubic发现丢包并判断为拥塞时,拥塞可能已经缓解,但cubic没有发现。为什么?原因是当cubic计算新的cwnd时,它不会以当前网络状态(如bw)为参数,只是按照数学意义上的三次方程盲目计算。这是错误的,而且它这不是一个正确的反馈系统!

基于a)和b),看,这就是新的拥塞判断机制!考虑到丢包和RTT的增加;

b-1).如果丢包时有拥塞,测得的瞬时带宽肯定会降低;否则丢包,也就是拥塞,就是骗人的。

b-2).如果RTT增大时真的发生了拥塞,那么测得的瞬时带宽肯定是减小的,否则时延增大,也就是拥塞,就是骗人的。

Bbr测量实时带宽。cwnd和rtt的这种统一测量完全忽略了丢包。所以bbr的算法思路才是TCP拥塞控制的正道!其实丢包不应该是拥塞的标志,而是拥塞的一种表现。

3.状态机的一点一滴

我已经提出了启动,排水,探针_带宽,探针_RTT和一些细节的状态图。当时我就指出这个状态图的目标是完成bbr的目标,也就是全网填满!在这个状态图中,所有已知的都是当前的瞬时带宽,所有可以计算的都是增益系数。然后,基于这两个元素,可以容易地计算起搏速率和cwnd。不是这不是很简单吗?看似整体就这么简单,但从细节来看,不同管道状态下增益系数的计算值得推敲。以下是各种状态下bbr的增益系数:

启动:2~3

引流的增益系数:起搏率为1000/2885,cwnd的增益系数为1000/2005 1。

PROBE_BW: 5/4,1,3/4,bbr会在PROBE_BW期间在这些增益系数中随机选择当前的增益系数。

探测器_RTT:1 .但是在检测RTT的过程中,为了防止丢包,cwnd会强制切到最小值,也就是4 MSS。

正如我们所看到的,没有明确的所谓车窗下降时刻在bbr。一切都按照国家机器来,我们不不关心TCP是否处于开放、恢复和其他状态。在以往的拥塞控制算法中,除Vegas等基于时延的算法外,当计算出的目标cwnd小于当前cwnd时,就会被视为拥塞而掉出窗口。当检测到分组丢失(RTO或重新排序重复ACK)时,所有其他基于分组丢失的算法将退出窗口。可悲的是,这个掉窗过程不受拥塞算法控制,拥塞算法只能负给一个ssthresh值,也就是掉窗目标,这显然是无奈之举。

Bbr不再关注丢包事件。这不don’不要把丢包当作一件严重的事情,它不会。我不属于它。只要TCP拥塞状态机控制机制能够合理地将一些数据包标记为丢失,然后重新传输,bbr所能做的就是告诉TCP它可以发送多少数据,即这就是全部!然而,如果TCP不不能正确标记丢失的数据包,bbr不能 care,它只是根据当前的bw和增益系数给出下一次的起搏速率和cwnd!

4.关于斯赫德FQ

这涉及到bbr以外的东西,公平排队!在bbr 的补丁,你会发现几行注释:

注意:BBR *必须*与启用起搏的FQ qdisc(“man TC-FQ”)一起使用,因为起搏是BBR设计和实施的组成部分。没有调步的BBR将不能正常工作,并且可能导致不必要的高分组丢失率。

记住这些台词并理解它们。

这是bbr最重要的方面。尽管Linux的TCP实现很早就支持调步速率,但直到版本4.8才在TCP级别得到支持。很大一部分原因是起搏率可以借助现有的FQ完美实现!TCP可以使用FQ来实现温和而不是突然的数据传输!

有关FQ的详细信息,请参考相关手册和源代码。这里您想说的是,FQ可以将cwnd中的数据传输从突发到网络到温和传输到网络根据bbr设置的起搏速率。所谓温柔传输,就是数据包按照带宽速率一个一个的发送到网络中,而不是突发到网络中!

这样就给了网络缓存一个缓解的机会!记住,关键问题是bbr每次收到ACK/SACK都会计算bw,这个精确的测量不会错过任何机会。即使当前网络拥塞,只要下一刻能恢复,bbr也能找到,所以即时带宽通常能说明这一点!

5.其他的

还有令牌桶监管发现(lt监管)和长期采样的主题。让让我们留待后面的文章来阐述。这篇文章够长了。

6 .缓冲漂浮问题

关于深队列,如何长时间排队不丢包造成RTO,对于浅队列如何频繁丢包。一开始想说这个话题,后来想骂人,现在守口如瓶!大家都知道端到端的QoS是一个典型的反馈系统,但是大家只是说了很多。我选择保持沉默。如果非要我说,我的回答是:我不我不知道!

这是一个可以说对错的话题,就像股票预测一样,所以我选择闭嘴。

bbr算法到来后,单从公开的测试结果来看,似乎已经解决了bufferbloat问题。也许,也许。Bbr好像真的开始在高速公路上飙车了。最后给出一个《A quick look at TCP BBR》的测试图:

bbr代码的简单性和复杂性

我一直觉得TCP拥塞控制算法太复杂,复杂的东西基本都是用来装的垃圾,直到我遇到bbr。

Neal Cardwell提供的补丁简单直接。可以看看这个bbr的补丁!在bbr模块之外,Neal Cardwell主要改变了tcp_ack函数和主拥塞控制函数中关于delivered count的部分,这些都是非常明显的,只要打补丁代码就能一目了然。当一个包被发送时——不管是第一次发送还是重发,都会被当前的tcp连接状态记录在包的tcp_skb_cb中。当数据包被应答时——无论是ACK还是SACK——它将根据当前状态及其在tcp_skb_cb中的状态计算带宽。这些显而易见的逻辑应该比任何人都清楚代码修改到了哪里!

但是这种查找确认工作太悲催了,很容易读码,移植代码也没意思,因为时间卡的太紧了!我要说的是,如果一件有趣的事情变成了一份必要的工作,那么做这件事的激情至少会降低1/4。好吧,这it’还不算太糟。但是,如果必要的工作有期限,那么激情又会减少1/4。最后,如果有人一直在背后推搡,那结束了,而且可以马上结束。但我可以郑重声明,这是凑合的结果!但其实这件事早就应该马上快速高质量的完成验收了!

写在最后

我更喜欢匠人精神,一种用时间打磨精品的精神,一种自由引导创作的精神。