TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

本文旨在帮助大家理解TCP CUBIC拥塞控制算法背后的点点滴滴以及其方程式为什么就是那样子的。一直以来,很多人都觉得CUBIC算法非常复杂,涉及到复杂的天书般的”3次曲线“...然而,CUBIC并不像大家以为的那样复杂,之所以觉得复杂是因为没有理解其历史和背景。

本文就是介绍CUBIC的历史和背景的...

BIC算法

BIC算法对窗口可能的最大值进行二分查找,它基于以下的事实:

1.如果发生丢包的时候,窗口的大小是W1,那么要保持线路满载却不丢包,实际的窗口最大值应该在W1以下;
2.如果检测到发生丢包,并且已经将窗口乘性减到了W2,那么实际的窗口值应该在W2以上。


        因此,在TCP快速恢复阶段过去之后,便开始在W2~W1这个区间内进行二分搜索,寻找窗口的实际最大值。于是定义W1为Wmax,定义W2为Wmin。

        以上说的是”道“,接下来我们看一下”术“,即如何驱动整个二分搜索的过程,非常简单!采用ACK驱动:

每收到一个ACK的时候,便将窗口设置到Wmax和Wmin的中点,一直持续到接近Wmax。

可见BIC的行为是ACK驱动的,而ACK在什么时候到来是与RTT相关的。这里,我们先留下一个问题。

        在了解了”道“和”术“之后,我们来看一下”势“。道只是一个原则,对待所有人都是一样,而术则是实现这个原则的途径,每个人都有不同,然而最终决定谁能独霸天下的是势,这就是说,在领会了道,并且得到了术之后,接下来就要进入争夺阶段了。BIC算法已经在没有丢包的情况下无限接近了Wmax,这意味着带宽有空闲资源了,此次的最大带宽已经不止Wmax了,而是大于Wmax的一个值!问题是,如何找到它!在说如何找到它的方法之前,先说一下怎么知道已经找到了它,答案就是丢包。

        超越Wmax之后,如何找到新的Wmax呢?

        也非常简单!既然度过了Wmax都没有丢包,说明新的Wmax还没有达到,此时BIC采取了一种非常简单直接的方法:按照逼近Wmax的路径倒回去,即采用与之对称的方案。

        以上整个的道,法,势三者总结成了以下的图示:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

两个RTT不同的连接,其通过BIC算法搜索到Wmax的时间是不同,进而其进入Max-Probe阶段也是不同的,因此空闲的带宽会被RTT短的那个连接无情的占有:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

当然,BIC算法远不止上面说的那么简单,以上的图解仅仅是理论上的,涉及到实现的时候就不得不考虑TCP运行时的行为特征,比如下图所示的问题:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

这带来了BIC实现上的丑陋和复杂性,会有很多的if-else出现,这在感官上很不舒服。

----------------------------------------------------------------

我们已经看到了BIC的半景全貌,知道了它存在的两个问题:

公平性的问题

TCP对带宽的利用并不是亲王扫六合的过程,更像是近代欧洲均势。所以说,抢带宽的算法都是傻逼算法。

感官上不那么美观的问题

数学上追求的是既然原理上是一个公式,在实现上也必须是一个公式,不能加入更多的现实约束。

----------------------------------------------------------------

解决这些问题的过程导致了CUBIC的诞生!

值得注意的是,BIC和CUBIC在命名上特别有意思,BIC是Binary Increase Congestion的首字母缩写,而CUBIC并不是BIC加上了"CU-"前缀(-CU前缀是什么??)...CUBIC不是什么缩写,它本来就是”立方“的形容词全称,而立方在数学上也是”3次方“的意思,因此顾名思义,CUBIC采用了数学函数即cubic curve的方式,而不是通过ACK到达后的处理行为(B)inary (I)ncrease (C)ongestion来探测窗口。在命名上,这是值得注意的。

注解:后面由于会涉及到简单的数学公式,由于在文字中插入希腊字母很不方便,因此凡是数学公式中的希腊字母,我在正文中一律写成其英文音译,比如我会用beta来表示希腊字母里相应读音的那个很像大写B的字母。

CUBIC算法

我们先看下CUBIC是怎么做到窗口探测过程与RTT无关的。

        要做到一个在时间上公平的窗口探测行为,就要让所有的连接的类似BIC那样的探测曲线是一致的,也就是说所有TCP连接的探测曲线相同!怎么做到的呢?很简单。找一条数学上定义的曲线即可!该曲线的曲线方程自变量里没有RTT就好了。

        我们知道,在二维笛卡尔坐标系中,曲线的自变量只有一个,我们定义其为绝对时间即可,而这只与从曲线开始画到收到ACK的绝对时间差有关,绝对时间无论对待谁都是公平的!现在的问题是,如何定义曲线本身,让其看上去比较像BIC的探测曲线。首先一个问题,它应该是几次曲线呢(我们当然已经知道了它是3次曲线,但这里给的是一个过程而不是结论!)?

        我们注意观察BIC的曲线,我们发现这是一条其增长率开始很大,慢慢变小,趋近于0,然后再慢慢变大的过程...

        考虑到了变化率,不由就想到了导数。现在的问题转化为了:

谁的导数是慢慢变小,变成0后再慢慢变大的呢?

在找到最终的曲线之前,我们先要找到导数本身的曲线,然后升阶即可。问题越来越显得容易了。学过高中数学的人基本都知道二次曲线满足这个导数曲线的要求,我们以下面这个二次曲线为例:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

然后画出其图形后发现它可以完美诠释BIC窗口探测曲线的斜率走势:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

这条导数曲线对应的原始曲线就是一条3次曲线:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

以上这个仅仅是个例子,我们的目的在于确定CUBIC的曲线形状,是的,最终的CUBIC曲线就是这样子的,是不是跟BIC的窗口探测曲线很像呢?

        确定了曲线的形状之后,最终我们要确定曲线的参数,最终曲线的方程应该是:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

完成确定待定系数这一步,我们就可以彻底理解CUBIC那看似复杂的方程式背后的所有了。

        确定待定系数的第一步,也是最重要的一步,就是确定曲线方程的形式。这一步很重要!它甚至比对曲线的形状的认知更加重要。如果没有对方程形式的确定,仅仅依靠曲线上的两个点来待定3次曲线方程的系数,就像解不定方程一样,变成一种技巧活了。反之,一旦确定了曲线方程的形式,那么求待定系数的过程就非常简单了!

        我们知道,曲线是对称的,由凸凹两部分组成(这到底是CUBIC曲线拟合BIC曲线,还是要反过来呢? :-(),由于CUBIC曲线事实上是一个有界的对称曲线,在最终参数确定的时候,曲线在横轴的中点r也是确定的,我们观察最终的曲线,发现横轴中点位置正好在纵轴到达Wmax,再看曲线的左界,当曲线在初始的时刻,W从快速恢复的结束位置开始增长,即ssthresh这个值的位置:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

这个位置,就是beta*Wmax!

        我们要做的是使用待定系数法来构造方程,然后求出这些系数并给出其物理意义。

        但我们不是真的要按照f(x)的二项式表示去求其系数a,b,c,d,而是将其构造成更加合理的形式。

注意到曲线上有三个点是特殊的,如下图所示:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

并且注意到曲线本身是关于P(x=r,y=Wmax)这个点对称的,我们确定曲线方程的常数因子就是Wmax。因此,我们可以简单的将曲线方程写成以下的形式:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

现在的目标就是求h(x)。

        注意到另外两个点对曲线的约束,从曲线关于点P(r,Wmax)对称,我们可以得知:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

以及

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

其中

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

由以上三式(1),(2),(3)以及(0)得知:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

由(4),(5),我们可以构造出h(x)的一个合理形式:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

根据上述的(4)和(5),我们得到r的表达式:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

##不得不承认以上是一个很合理的解

到这里,你可能觉得这越来越复杂了,但事实上,这推导非常简单,而且,推导结束了,这就是CUBIC的最后结果!我们把上式中的系数a换成C(CUBIC的
Paper上写的就是C,另外,r也要换成K...),那么最终的式子就是:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

OK!结束了。现在我们来看下这个式子里面的两个参数beta和C,分别代表什么,或者更加形象的,它们分别控制着什么。

        对于一次从快速恢复结束的时刻到丢包的拥塞避免过程,我们假设发生丢包时的窗口大小为Wmax,并且假设网络中没有别的连接,那么窗口探测曲线在每一轮拥塞探测过程都是相同的,因此,我们认为Wmax是常数。非常显然,beta决定了整个曲线对称范围围成区域的高度,而r则控制了从起始窗口到达丢包窗口的时间,由于r与C成反比,那么C越大,则探测到最大窗口的时间越短,反之则越久。由此,我们只要控制beta和C两个参数,就能控制CUBIC算法的行为了。

        现在知道CUBIC曲线的细节了吗?好吧,我们一直在数学公式里徘徊,现在终于要回到TCP了!我们看一下曲线中的beta和a(也就是C)分别影响了TCP的什么特性,正如CUBIC的
Paper上所述:

beta控制了TCP Friendliness
C控制了TCP的收敛速度

【注意,上面式子里的r就是K,CUBIC的Paper上说,K决定了从beta*Wmax到Wmax的时间:K is the time period that the above function takes to increase W to Wmax when there is no further loss event

由于CUBIC曲线在稳定拥塞避免阶段(Steady State)和空闲资源探测阶段(Max Probing)是对称的,因此我们把收敛速度理解成两个方面,一方面,它决定了在稳定的拥塞避免阶段TCP的拥塞窗口到达最大窗口的时间,另一方面,它在空闲资源的探测阶段又决定了探测到新的最大窗口所需的时间。这种对称的行为,它真的合理吗??

        最后,我将CUBIC的算法运行和通用的TCP拥塞控制算法进行映射,我发现它毫不特殊,运行CUBIC算法的TCP连接,也分别会将运行阶段映射为三个阶段中的一个,分别为稳定阶段(Steady State),即不丢包的阶段,新空闲资源探测阶段,即探测新最大窗口的阶段(Max Probing),以及公平收敛阶段,即减少数据发送量的阶段(类似PRR降窗过程),和bbr不同的是,CUBIC的公平收敛阶段不是自己算法控制的(PRR),而bbr则是自己控制的(默认运行1个周期,其后持续运行6个恒稳周期,bbr的周期是一个伺服循环:1个Max Probing-1个Drain-6个Steady State-...)。CUBIC的总体运行如下图所示:

TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

后记

到此,我觉得本文该要结束了。我个人认为,本文最大的成就感在于,比较明了地解释了CUBIC这个3次曲线的内部到底是什么样子的以及为什么是这个样子。很多人搞不清楚它的真面目,觉得它怎么可以这么复杂,事实上,CUBIC一点都不复杂!它是由BIC算法一步步衍生出来的。如果按照正常的推导,那些参数都是可以自己确定的,毫无任何神秘之处。那么BIC算法呢?更加简单,收到ACK包后执行二分查找驱动的一个过程而已。

        在我分析了bbr算法之后,我曾经说过,bbr算法非常简单,到此为止,你是不是也觉得CUBIC也是很简单呢?在你给出肯定回答之前,我必须澄清它们的一点区别。

        bbr算法就是其行为本身的描述,而CUBIC算法则是借用了一条数学上很优美的曲线方程,这条3次曲线并不关联任何TCP的行为,换句话说,如果有更好的选择,也可以不使用这条曲线。这一点与ECC算法(椭圆曲线加密算法)的背景十分不一样,ECC算法脱离不了椭圆曲线,正是在该曲线上的群,环,域的计算定义了整个算法过程。区别这两点,对于优化CUBIC是十分重要的。

附1:优化和灵活性

既然TCP拥塞算法是借用了一条3次曲线,那么这条曲线就不是必须的!仔细观察本文最后的那幅图,和BIC算法一样,CUBIC算法在稳定阶段和探测阶段是对称的,但是非得这样吗?

        如果我明明知道在探测到Wmax的时候已经近乎拥塞,那CUBIC曲线还傻傻的去猛往上探测直到丢包,这样还合适吗?显然是不合适的。因此我们可能需要一条不对称的CUBIC曲线!比如在稳定阶段陡一点,在探测阶段缓一点,这可能最终会进化到bbr算法类似的效果!

        但是,我真的需要另外一条曲线吗??完美主义者或者数学洁癖者可能特别希望用一个方程式描述这种不对称的曲线,但如果仅仅想在工程上实现这个理念,完全没有必要去寻找什么曲线,直接使用不对称的曲线即可。

        我不再深入阐述这个话题,我只展示一下Linux CUBIC的代码之修改。对bictcp_update进行修改:

curr_C = cube_rtt_scale;
// base_mdev的含义是,刚进入拥塞避免稳定状态时的mdev,而curr_mdev则是当前的mdev
if (ca->base_mdev && ca->curr_mdev && t > ca->dragon_K) {
    if (ca->curr_mdev > ca->base_mdev) // 如果RTT变得比较更加抖动,说明丢包可能性比较大
        curr_C >>= 1;  // 仅为一例,旨在伸展曲线宽度
    if (ca->base_mdev < ca->curr_mdev)
        curr_C <<= 1;  // 旨在压缩曲线宽度
}
delta = (cube_rtt_scale * offs * offs * offs) >> (10+3*BICTCP_HZ);
...

附2:总结和记录

为什么在丢包的时候窗口要减到一个很低的值,而不是仅仅减去1或者2呢?请研究我之前贴的那个TCP公平性收敛图。

CUBIC曲线由参数确定。

BIC曲线由行为确定。

BIC/CUBIC的收敛靠事件触发,bbr收敛靠自身伺服机制触发。

原文链接: https://blog.csdn.net/dog250/article/details/53013410

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    TCP拥塞控制算法-从BIC到CUBIC_cubic算法详解

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/407756

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午10:39
下一篇 2023年4月26日 上午10:39

相关推荐