TCP拥塞控制算法的目的可以简单概括为:公平竞争、充分利用网络带宽、降低网络延时、优化用户体验,然而就目前而言要实现这些目标就难免有权衡和取舍。

算法分类

基于丢包策略的传统拥塞控制算法的几个迭代版本,如图所示:

基于python的拥塞控制算法模拟 拥塞控制的几种算法_丢包

与此同时还有一类算法是基于RTT延时策略来进行控制的,但是这类算法在发包速率上可能不够激进,竞争性能不如其他算法,因此在共享网络带宽时有失公平性,但是算法速率曲线却是很平滑

基于python的拥塞控制算法模拟 拥塞控制的几种算法_tcpip_02

基于链路容量的拥塞控制:实时测量网络带宽和时延,认为网络上报文总量大于带宽时延乘积时出现了拥塞,如 BBR。

基于学习的拥塞控制:没有特定的拥塞信号,而是借助评价函数,基于训练数据,使用机器学习的方法形成一个控制策略,如 Remy。

如何感知拥塞

基于各种拥塞策略的拥塞控制算法有不一样的拥塞判断标准:

  1. 基于丢包
    丢包可以由重传超时RTO和重复确认来做判断。
  2. 基于时延
  3. 基于链路容量
  4. 基于机器学习
    根据参数得出一个拥塞窗口值

拥塞控制基本策略

拥塞控制是一个动态的过程,它既要提高带宽利用率发送尽量多的数据又要避免网络拥堵丢包RTT增大等问题,基于这种高要求并不是单一策略可以搞定的,因此TCP的 拥塞控制策略实际上是分阶段分策略的综合过程

基于python的拥塞控制算法模拟 拥塞控制的几种算法_基于python的拥塞控制算法模拟_03

基于丢包的拥塞控制

Tahoe算法

如果收到三次重复确认即第四次收到相同确认号的分段确认,并且分段对应包无负载分段和无改变接收窗口的话,Tahoe算法则进入快速重传,将慢启动阈值改为当前拥塞窗口的一半,将拥塞窗口降为1个MSS,并重新进入慢启动阶段

拥塞控制过程大致如下:

基于python的拥塞控制算法模拟 拥塞控制的几种算法_tcpip_04

Reno算法(我们当前理解的算法)

如果收到三次重复确认,Reno算法则进入快速重传只将拥塞窗口减半来跳过慢启动阶段,将慢启动阈值设为当前新的拥塞窗口值,进入一个称为快速恢复的新设计阶段。

拥塞控制流程如下:

基于python的拥塞控制算法模拟 拥塞控制的几种算法_tcpip_05

Reno 算法将收到 ACK 这一信号作为拥塞窗口增长的依据,在早期低带宽、低时延的网络中能够很好的发挥作用,但是随着网络带宽和延时的增加,Reno 的缺点就渐渐体现出来了,发送端从发送报文到收到 ACK 经历一个 RTT,在高带宽延时(High Bandwidth-Delay Product,BDP)网络中,RTT 很大,导致拥塞窗口增长很慢,传输速度需要经过很长时间才能达到最大带宽,导致带宽利用率将低。

适用场景:

适用于低延时、低带宽的网络。

TCP New Reno是对TCP Reno中快速恢复阶段的重传进行改善的一种改进算法,New Reno在低错误率时运行效率和选择确认SACK相当,在高错误率仍优于Reno。

CUBIC算法

Cubic是 Linux 内核 2.6 之后的默认 TCP 拥塞控制算法, 使用一个立方函数(cubic function):
基于python的拥塞控制算法模拟 拥塞控制的几种算法_丢包_06
作为拥塞窗口的增长函数,其中,C 是调节因子,t 是从上一次缩小拥塞窗口经过的时间,基于python的拥塞控制算法模拟 拥塞控制的几种算法_丢包_07是上一次发生拥塞时的窗口大小,
基于python的拥塞控制算法模拟 拥塞控制的几种算法_tcpip_08

β是乘法减小因子。从函数中可以看出拥塞窗口的增长不再与 RTT 有关,而仅仅取决上次发生拥塞时的最大窗口和距离上次发生拥塞的时间间隔值。

Cubic 拥塞窗口增长曲线如下,凸曲线部分为稳定增长阶段,凹曲线部分为最大带宽探测阶段。如图下图 所示,在刚开始时,拥塞窗口增长很快,在接近 基于python的拥塞控制算法模拟 拥塞控制的几种算法_丢包_07 口时,增长速度变的平缓,避免流量突增而导致丢包;在 基于python的拥塞控制算法模拟 拥塞控制的几种算法_丢包_07 附近,拥塞窗口不再增加;之后开始缓慢地探测网络最大吞吐量,保证稳定性(在 基于python的拥塞控制算法模拟 拥塞控制的几种算法_丢包_07 附近容易出现拥塞),在远离 基于python的拥塞控制算法模拟 拥塞控制的几种算法_丢包_07

基于python的拥塞控制算法模拟 拥塞控制的几种算法_网络_13

当出现丢包时,将拥塞窗口进行乘法减小(拥塞窗口减小到当前的一半),再继续开始上述增长过程。此方式可以使得拥塞窗口一直维持在 基于python的拥塞控制算法模拟 拥塞控制的几种算法_丢包_07

基于python的拥塞控制算法模拟 拥塞控制的几种算法_基于python的拥塞控制算法模拟_15

Cubic 算法的优点在于只要没有出现丢包,就不会主动降低自己的发送速度,可以最大程度的利用网络剩余带宽,提高吞吐量,在高带宽、低丢包率的网络中可以发挥较好的性能。

但是,Cubic 同之前的拥塞控制算法一样,无法区分拥塞丢包和传输错误丢包,只要发现丢包,就会减小拥塞窗口,降低发送速率,而事实上传输错误丢包时网络不一定发生了拥塞,但是传输错误丢包的概率很低,所以对 Cubic 算法的性能影响不是很大。

Cubic 算法的另一个不足之处是过于激进,在没有出现丢包时会不停地增加拥塞窗口的大小,向网络注入流量,将网络设备的缓冲区填满,出现 Bufferbloat(缓冲区膨胀)。由于缓冲区长期趋于饱和状态,新进入网络的的数据包会在缓冲区里排队,增加无谓的排队时延,缓冲区越大,时延就越高。另外 Cubic 算法在高带宽利用率的同时依然在增加拥塞窗口,间接增加了丢包率,造成网络抖动加剧。

适用场景:

适用于高带宽、低丢包率网络,能够有效利用带宽。

基于链路容量的算法

BRR算法

BBR[4] 是谷歌在 2016 年提出的一种新的拥塞控制算法,已经在 Youtube 服务器和谷歌跨数据中心广域网上部署,据 Youtube 官方数据称,部署 BBR 后,在全球范围内访问 Youtube 的延迟降低了 53%,在时延较高的发展中国家,延迟降低了 80%。目前 BBR 已经集成到 Linux 4.9 以上版本的内核中。

BBR 算法不将出现丢包或时延增加作为拥塞的信号,而是认为当网络上的数据包总量大于瓶颈链路带宽和时延的乘积时才出现了拥塞。

BBR 算法周期性地探测网络的容量,交替测量一段时间内的带宽极大值和时延极小值,将其乘积作为作为拥塞窗口大小(交替测量的原因是极大带宽和极小时延不可能同时得到,带宽极大时网络被填满造成排队,时延必然极大,时延极小时需要数据包不被排队直接转发,带宽必然极小),使得拥塞窗口始的值始终与网络的容量保持一致。

什么叫做BDP呢?它叫做带宽时延积,例如一条链路的带宽是100Mbps,而RTT是40ms,那么

BDP=100Mbps*0.04s=4Mb=0.5MB

即平均每秒飞行中的报文应当是0.5MB。因此Linux的接收窗口缓存常参考此设置:

基于python的拥塞控制算法模拟 拥塞控制的几种算法_拥塞控制_16

事实上,我们的传输速度在3个阶段被不同的因素限制:

  1. 应用程序限制阶段,此时RTT不变,随着应用程序开始发送大文件,速率直线上升;
  2. BDP限制阶段,此时RTT开始不断上升,但吞吐量不变,因为此时瓶颈路由器已经达到上限,缓冲队列正在不断增加;
  3. 瓶颈路由器缓冲队列限制阶段,此时开始大量丢包。

如下所示:

基于python的拥塞控制算法模拟 拥塞控制的几种算法_基于python的拥塞控制算法模拟_17

如CUBIC这样基于丢包的拥塞控制算法在第2条灰色竖线发生作用,这已经太晚了,更好的作用点是BDP上限开始发挥作用时,也就是第1条灰色竖线。

而BBR通过检测RTpropBtlBw来实现拥塞控制。什么是RTprop呢?这是链路的物理时延,因为RTT里含有报文在路由器队列里的排队时间、ACK的延迟确认时间等。什么叫延迟确认呢?TCP每个报文必须被确认,确认动作是通过接收端发送ACK报文实现的,但由于TCP和IP头部有40个字节,如果不携带数据只为发送ACK网络效率过低,所以会让独立的ACK报文等一等,看看有没有数据发的时候顺便带给对方,或者等等看多个ACK一起发。所以,可以用下列公式表示RTT与RTprop的差别:
基于python的拥塞控制算法模拟 拥塞控制的几种算法_tcpip_18

RTT我们可以测量得出,RTprop呢,我们只需要找到瓶颈路由器队列为空时多次RTT测量的最小值即可

BtlBw全称是bottleneck bandwith,即瓶颈带宽,我们可以通过测量已发送但未ACK确认的飞行中字节除以飞行时间deliveryRate来测量

早在1979年Leonard Kleinrock就提出了第1条竖线是最好的拥塞控制点,但被Jeffrey M. Jaffe证明不可能实现,因为没有办法判断RTT变化到底是不是因为链路变化了,从而不同的设备瓶颈导致的,还是瓶颈路由器上的其他TCP连接的流量发生了大的变化。但我们有了RTprop和BtlBw后,当RTprop升高时我们便得到了BtlBw,这便找到第1条灰色竖线最好的拥塞控制点,也有了后续发送速率的依据。

由于 BBR 的拥塞窗口是精确测量出来的,不会无限的增加拥塞窗口,也就不会将网络设备的缓冲区填满,避免了出现 Bufferbloat (缓冲区膨胀)问题,使得时延大大降低。

如下图所示,网络缓冲区被填满时时延为 250ms,Cubic 算法会继续增加拥塞窗口,使得时延持续增加到 500ms 并出现丢包,整个过程 Cubic 一直处于高时延状态,而 BBR 由于不会填满网络缓冲区,时延一直处于较低状态。

基于python的拥塞控制算法模拟 拥塞控制的几种算法_tcpip_19

由于 BBR 算法不将丢包作为拥塞信号,所以在丢包率较高的网络中,BBR 依然有极高的吞吐量,如图 5下图所示,在 1% 丢包率的网络环境下,Cubic 的吞吐量已经降低 90% 以上,而 BBR 的吞吐量几乎没有受到影响,当丢包率大于 15% 时,BBR 的吞吐量才大幅下降。

基于python的拥塞控制算法模拟 拥塞控制的几种算法_丢包_20

BBR 算法是反馈驱动的,有自主调节机制,不受 TCP 拥塞控制状态机的控制,通过测量网络容量来调整拥塞窗口,发送速率由自己掌控,而传统的拥塞控制算法只负责计算拥塞窗口,而不管发送速率(pacing rate),怎么发由 TCP 自己决定,这样会在瓶颈带宽附近因发送速率的激增导致数据包排队或出现丢包。

经过测试,在高延时、高丢包率的环境下,BBR 相对于 Cubic 算法在传输速度上有较大的提升,具体的测试结果如下表所示:

基于python的拥塞控制算法模拟 拥塞控制的几种算法_基于python的拥塞控制算法模拟_21

BBR 算法的不足之处在于设备队列缓存较大时,BBR 可能会竞争不过 Cubic 等比较激进算法,原因是 BBR 不主动去占据队列缓存,如果 Cubic 的流量长期占据队列缓存,会使得 BBR 在多个周期内测量的极小 RTT 增大,进而使 BBR 的带宽减小。

适用场景:

适用于高带宽、高时延、有一定丢包率的长肥网络,可以有效降低传输时延,并保证较高的吞吐量。

基于学习的算法

Remy

Remy 也称为计算机生成的拥塞控制算法(computer-generated congestion-control algorithm),采用机器学习的方式生成拥塞控制算法模型。

略了吧…🙃

基于时延的算法

Vegas算法

Vegas将时延 RTT 的增加作为网络出现拥塞的信号,RTT 增加,拥塞窗口减小,RTT 减小,拥塞窗口增加。具体来说,Vegas 通过比较实际吞吐量和期望吞吐量来调节拥塞窗口的大小.

期望吞吐量为:
基于python的拥塞控制算法模拟 拥塞控制的几种算法_丢包_22
实际吞吐量为:
基于python的拥塞控制算法模拟 拥塞控制的几种算法_tcpip_23
定义一个它们之间的差距diff:
基于python的拥塞控制算法模拟 拥塞控制的几种算法_网络_24
BaseRTT 是所有观测来回响应时间的最小值,一般是建立连接后所发的第一个数据包的 RTT,cwnd 是目前的拥塞窗口的大小。Vegas 定义了两个阈值 a,b,当 diff > b 时,拥塞窗口减小,当 a <= diff <=b 时,拥塞窗口不变,当 diff < a 时,拥塞窗口增加。

Vegas 算法采用 RTT 的改变来判断网络的可用带宽,能精确地测量网络的可用带宽,效率比较好。但是,网络中 Vegas 与其它算法共存的情况下,基于丢包的拥塞控制算法会尝试填满网络中的缓冲区,导致 Vegas 计算的 RTT 增大,进而降低拥塞窗口,使得传输速度越来越慢,因此 Vegas 未能在 Internet 上普遍采用。

适用场景:

适用于网络中只存在 Vegas 一种拥塞控制算法,竞争公平的情况。

基于python的拥塞控制算法模拟 拥塞控制的几种算法_tcpip_25