4.2 加法递增/乘法递减(AIMD)
Last updated
Last updated
准确的超时时间是TCP必不可少的构成元素,但它并不是拥塞控制算法的核心,拥塞控制的核心挑战在于预估TCP发送端可以安全的发送多少数据量(注,这同样也是TCP流控的核心问题)。为此,TCP 为每个连接维护了一个新状态变量,我们称之为拥塞窗口/CongestionWindow
(但你经常会在文献中看到它被称为 cwnd
,这是拥塞窗口在代码中对应的变量名)。这个变量被TCP的发送端用来限制在特定时间它可以向网络中传输多少数据。
CongestionWindow
之于拥塞控制,等于AdvertisedWindow
之于流量控制。在TCP最开始的版本中(注,最早的TCP未包含拥塞控制算法),TCP的发送端被允许发送AdvertisedWindow
个字节的未确认数据到网络中。加入拥塞控制之后,TCP的发送端被允许发送 min(AdvertisedWindow
, CongestionWindow
)个字节的未确认数据到网络中。对应的,第二章中的 EffectiveWindow
运算被修改成如下:
所以,现在TCP发送端的发送速率受限于(1)网络(2)接收端,两个中更慢的那个(注,CongestionWindow表示了网络转发处理的快慢,AdvertisedWindow表示了接收端处理的快慢)。
现在的问题变成了,如何确定CongestionWindow
的值。不像AdvertisedWindow
是由TCP接收端发送出来的,没有人会发出一个确定的CongestionWindow
。所以,TCP的发送端需要基于其感知的网络的拥塞状态,来自己决定CongestionWindow
的值。具体来说,就是当网络的拥塞程度上升时,降低拥塞窗口的大小,从而减少发送到网络中的数据量,以减少网络的拥塞程度;当网络的拥塞程度下降时,增大拥塞窗口的大小,从而增加发送到网络中的数据量,以充分利用网络的带宽。具体如何降低,如何增加,TCP拥塞控制算法通常采用一种策略叫做:加法递增/乘法递减(additive increase/multiplicative decrease, AIMD)。
现在我们知道了在拥塞控制领域,CongestionWindow
确定了数据发送的速率(注,真实的TCP,数据发送速率由拥塞控制和流量控制共同决定);我们也只知道了CongestionWindow会随着网络的拥塞程度进行调节,对应的策略时AIMD。那问题就变成了:TCP的发送端如何知道当前网络的拥塞程度?答案是根据丢包来判断。
当TCP segment 在超时时间之后还没有收到ACK,那么就TCP发送端认为该 segment 未能成功送达,并且很有可能是因为网络当前处于拥塞状态而导致丢包了。虽然存在除了网络拥塞以外的其他原因会导致丢包,例如CRC错误,但是这里TCP就是单纯的无脑的将超时认为是丢包,将丢包认为是网络拥塞。
所以,当TCP发现超时发生了,就会降低其发送速率。而怎么降低呢?具体来说就是将CongestionWindow
减半。这里的减半行为对应了AIMD里面的乘法递减(注,每次乘以0.5)。
尽管CongestionWindow
的值是以字节为单位,为了更好的理解乘法递减,我们接下来还是基于packet来考虑CongestionWindow
。假设CongestionWindow
当前是16个packets大小,当丢包被探测到的时候(也就是超时发生了),CongestionWindow
会被设置为8。(通常来说,丢包探测基于超时,但我们在后面的内容会发现,TCP还有另一种机制来探测丢包)如果再来一次丢包,CongestionWindow
会被设置为4,再来就是2,再来就是1。CongestionWindow
不允许被设置成小于一个packet的大小,对应字节数就是我们在第二章学过的MSS
。
如果拥塞控制算法只会减少窗口的大小,那它就太过保守了,并且对应的网络带宽不能得到充分的利用。所以,拥塞控制算法还要能够增大CongestionWindow
来充分利用当前可用的网络容量。这就是AIMD里面的“加法递增”部分。每当TCP发送端成功送出了与CongestionWindow
数量相当的packets之后(注,这里所谓的成功送出是指packet都被发出去了,且对应的ACK都被收到了),TCP会增加CongestionWindow
相当于1个packet的大小。这里递增的效果如图22所示。
实际中,TCP并不会等待整个CongestionWindow
对应的所有packet都被ACK之后再增加一个packet到CongestionWindow
,而是每次收到一个ACK就给CongestionWindow
加一点点。具体来说,每当ACK收到了,拥塞窗口会按照下面的公式增加:
也就是说,TCP并不是在每个RTT后,再将CongestionWindow
增大MSS个字节(注,这里的MSS就等同于上面说的packet大小),而是在每收到一个ACK之后,就给CongestionWindow
增大MSS
的一部分。假设每个ACK确认收到了MSS
个字节,那么这里的一部分就是MSS/CongestionWindow
。
这种连续的增加减少拥塞窗口的行为会在整个TCP连接中会一直持续。如果你将CongestionWindow的值画一个基于时间的曲线,你会得到一个锯齿状的图,如图23所示。
AIMD包含了一个很重要的设计理念:相比增加拥塞窗口的大小,TCP的发送端愿意以一种快得多的方式减少拥塞窗口。假设采用加法递增/加法递减的策略来管理拥塞窗口,也就是每次超时了减少拥塞窗口一个MSS,那么这种方式相对来说太过激进了(注,这里的激进是指对于网络资源的消耗,当拥塞发生时,如果只是慢慢的减少发送速率,那对于网络资源的消耗来说就是太过激进了)。当发生拥塞时,进行快速的响应对于网络稳定性来说非常重要。
对于为什么TCP激进的减少,保守的增加拥塞窗口的大小,有一个直观的解释:窗口过大会导致问题不断累积。假设窗口过大,那么被丢弃的packet会被重传,从而使得拥塞更加严重。如果拥塞发生了,第一要务就是从这种状态中解脱出来。你可以认为AIMD会慢慢的增加传输的数据量来探测拥塞何时发生,然后一旦超时了就认为达到了拥塞,并迅速的从拥塞的边缘回退。
最后,由于超时代表了拥塞,并且会触发乘法递减,TCP需要最精确的超时机制。我们在4.1中已经讨论过了TCP的超时时间计算机制,超时时间由平均的RTT和其波动值决定。