前言
TCP的流量控制服务完成了对发送方发送速率的调节——当TCP通信的接收方的接收速率无法匹配发送速率时,发送方会降低发送速率。但流量控制没有考虑到整个网络中的情况——即使路由器能够存储一些数据,但若源源不断的数据到达速率高于路由器的发出速率,任何容量的路由器都会溢出。
路由器因无法处理高速率到达的流量而被迫丢弃数据的现象被称为——拥塞。
从流量控制服务可知,针对于丢包情况,TCP采取的首要机制是重传,包括超时重传和快速重传。但如果网络已经处于了拥塞状态,你再进行重传,会导致同一时间段内网络传输路径上需要传输更多的数据包。这就好比火上浇油,拥塞的情况会更加严重。
所以,拥塞控制的原则为:
- 当拥塞状况出现时,我们就需要减缓TCP发送端的发送速率。
- 当拥塞状况好转时,我们就需要检测和使用新的可用带宽。
如何检测拥塞
但是检测拥塞却很难做到,因为对于TCP的发送方来说,没有一个精确的方法去知晓中间路由器的状态。换言之,没有一个明确的信号告知拥塞状况已经发生。
往深了说,网络层没有为运输层拥塞控制提供显示支持。即使网络中存在拥塞,端系统也必须通过对网络行为的观察(如分组丢失与时延)来推断。
有两种行为可以观察:
拥塞控制方法分类
- 端到端拥塞控制。端系统通过观察丢包行为(通过超时或3次冗余ACK而得知)。
- 网络辅助的拥塞控制。网络层构件(即路由器)向发送方提供关于网络中拥塞状态的显式反馈信息,一般来说我们不能依靠这种方式。分为两种:
- 路由器直接反馈信息给发送方。
- 路由器标记或更新从发送方流向接收方的分组中的某个字段来表示拥塞的发生。接收方收到这个分组后,再向发送方通知网络拥塞。这种形式更慢,需要一个完整的往返时间。
通过窗口减缓TCP发送
在流量控制服务中,用接受窗口
r
w
n
d
rwnd
rwnd来限制发送方可以发送的数据量。在拥塞控制中,我们用拥塞窗口
c
w
n
d
cwnd
cwnd(congestion window)来限制。那么发送方实际可用窗口
W
W
W为二者的较小者:
W
=
m
i
n
{
c
w
n
d
,
r
w
n
d
}
W = min\{ cwnd, rwnd\}
W=min{cwnd,rwnd}
为了仅关注于拥塞控制,本文假设TCP接受缓存无限大,即
r
w
n
d
rwnd
rwnd无限大。那么
W
=
c
w
n
d
W = cwnd
W=cwnd。 且已发送、待确认的数据量不会超过
W
W
W:
L
a
s
t
B
y
t
e
S
e
n
t
?
L
a
s
t
B
y
t
e
A
c
k
≤
W
LastByteSent - LastByteAck \leq W
LastByteSent?LastByteAck≤W
已发送、待确认的数据量有时也被称为在外数据量(flight size)。
拥塞控制算法
主要包含这三部分:
- 慢启动
- 拥塞避免
- 快速恢复
慢启动和拥塞避免是TCP的强制部分,二者的差异在于:对收到的ACK做出反应时增加
c
w
n
d
cwnd
cwnd长度的方式。 快速恢复是推荐部分,不是必需的。
理论基础
慢启动和拥塞避免是基于包守恒和ACK时钟原理的。 守恒指的是——某个量进入一个系统不会凭空消失或出现,而是以某种表现形式继续存在。在上图中,上面的数据包在被接收方接收后就会变成下面的ACK包。
在高效传输的稳定状态下,上下通道都不会出现包堵塞的情况,而且在上通道中也不会有较大传输间隔。注意到,每当发送包接收到一个ACK,就代表其可以向上层通道发送一个数据包。这种由一个ACK到达(称为ACK时钟)触发一个新数据包传输的关系称为自同步(self-clocking)。
状态迁移图
s
s
t
h
r
e
s
h
ssthresh
ssthresh为slow start threshold即慢启动阈值。
- 当
c
w
n
d
<
s
s
t
h
r
e
s
h
cwnd < ssthresh
cwnd<ssthresh时,使用慢启动算法。
- 当
c
w
n
d
>
s
s
t
h
r
e
s
h
cwnd > ssthresh
cwnd>ssthresh时,使用拥塞避免算法。
- 当
c
w
n
d
=
s
s
t
h
r
e
s
h
cwnd = ssthresh
cwnd=ssthresh时,任何一种算法都可以。
有两种ACK:
- new ACK,指发送方收到的ACK包里的Ack序号是大于
L
a
s
t
B
y
t
e
A
c
k
LastByteAck
LastByteAck的。
- duplicate ACK,指发送方收到的ACK包里的Ack序号是等于
L
a
s
t
B
y
t
e
A
c
k
LastByteAck
LastByteAck的。
慢启动
当一个新的TCP连接建立、或检测到 由超时引起的重传 时,需要执行慢启动。TCP发送端长时间处于空闲状态也可能调用慢启动算法。慢启动的目的是,使TCP在使用拥塞避免算法之前就找到一个
c
w
n
d
cwnd
cwnd值,以及帮助TCP建立ACK时钟。
开始时,
c
w
n
d
cwnd
cwnd的值通常设置为一个MSS的较小值,这使得初始发送速率大约为
M
S
S
R
T
T
\frac{MSS}{RTT}
RTTMSS?。 该算法的过程为:发送方每收到一个ACK,
c
w
n
d
cwnd
cwnd就增加一个MSS。这样,发送方每经过一个RTT的时间,
c
w
n
d
cwnd
cwnd就会翻倍。从上面的折线图,可以看到窗口大小呈指数形增长(只关注上面那根折线的话)。
慢启动的执行过程如同上图左边的过程:
- 初始时,
c
w
n
d
cwnd
cwnd的值为1个MSS。
- 在第1阶段开始时发送了一个数据包(因为
c
w
n
d
cwnd
cwnd=1MSS),结束时收到了一个ACK。
- 因为收到了一个ACK,所以
c
w
n
d
cwnd
cwnd增加1个MSS,变成2个MSS。
- 在第2阶段开始时发送了两个数据包(因为
c
w
n
d
cwnd
cwnd=2MSS),结束时收到了两个ACK。
- 因为收到了两个ACK,所以
c
w
n
d
cwnd
cwnd增加2个MSS,变成4个MSS。
- 以此类推
如上图,当
c
w
n
d
>
s
s
t
h
r
e
s
h
cwnd > ssthresh
cwnd>ssthresh时,转而使用拥塞避免算法。
如上图,这三个状态(包括慢启动自身)当遇到超时后(遇到超时,说明网络已经拥塞了),都会执行:
-
s
s
t
h
r
e
s
h
ssthresh
ssthresh设为
c
w
n
d
cwnd
cwnd的一半。
-
s
s
t
h
r
e
s
h
ssthresh
ssthresh变小,那么就会更快地从慢启动转到拥塞避免。
-
c
w
n
d
cwnd
cwnd设为1MSS。
- 让窗口回到初始值,并重新通过指数增长来探索到一个合理的窗口值。
拥塞避免
如上图,在慢启动阶段,
c
w
n
d
cwnd
cwnd会快速增长,并且会确定一个可能小于64KB的
s
s
t
h
r
e
s
h
ssthresh
ssthresh。如上图,整个慢启动过程中,可能遇到多次超时,每次超时后,将
s
s
t
h
r
e
s
h
ssthresh
ssthresh减半,让
c
w
n
d
cwnd
cwnd重新开始指数增长,最终
c
w
n
d
cwnd
cwnd会超过
s
s
t
h
r
e
s
h
ssthresh
ssthresh。值得注意的是,在离开慢启动前的最后一次指数增长过程中,是没有遇到过timeout的,也就是说,理想的窗口大小是大于等于这个
s
s
t
h
r
e
s
h
ssthresh
ssthresh的(让窗口大小保持为
s
s
t
h
r
e
s
h
ssthresh
ssthresh也可以,因为肯定不会造成网络拥塞。但是太过保守,有可能存在一个更大的值并且这个值也不会造成网络拥塞)。
一旦超过了由慢启动阶段确定的
s
s
t
h
r
e
s
h
ssthresh
ssthresh值,就意味着有更多可用的传输资源。如果立即全部占用这些资源,可能会使路由器出现严重丢包,从而使得网络不稳定。为了得到更多的传输资源同时不会影响其他TCP连接传输,所以我们要转而使用拥塞避免算法。 进入拥塞避免后,
c
w
n
d
cwnd
cwnd每次的增长值近似于成功传输的数据段大小,即每个RTT只将
c
w
n
d
cwnd
cwnd的值增加一个MSS。从上面的折线图可以看到,
c
w
n
d
cwnd
cwnd呈线性增长。
拥塞避免的执行过程如同上图左边的过程:
- 每收到一个ACK,
c
w
n
d
cwnd
cwnd增加
M
S
S
?
M
S
S
c
w
n
d
MSS \cdot \frac{MSS}{cwnd}
MSS?cwndMSS?。
- MSS是每个数据包payload的大小,
c
w
n
d
cwnd
cwnd是窗口内可以发送数据的总和,所以在每个RTT刚开始时,会发送
c
w
n
d
M
S
S
\frac{cwnd}{MSS}
MSScwnd?个数据包出来。
- 正常情况下,在每个RTT快结束时,也会收到
c
w
n
d
M
S
S
\frac{cwnd}{MSS}
MSScwnd?个ACK,那么
c
w
n
d
cwnd
cwnd总共增加
M
S
S
?
M
S
S
c
w
n
d
?
c
w
n
d
M
S
S
MSS \cdot \frac{MSS}{cwnd} \cdot \frac{cwnd}{MSS}
MSS?cwndMSS??MSScwnd?即刚好一个MSS。
在拥塞避免阶段,
c
w
n
d
cwnd
cwnd的变化过程为:每个RTT内
c
w
n
d
cwnd
cwnd线性(加性)增加1MSS,然后出现3个冗余ACK时,
c
w
n
d
cwnd
cwnd减半(乘性减)。因此,TCP拥塞控制常常被称为 加性增、乘性减(Additive-Increase, Multiplicative-Decrease,AIMD) 的拥塞控制方式。
快速恢复
能收到冗余ACK,说明网络可能有点拥塞,但是也没那么严重,所以进入到快速恢复这种中间状态,需要进一步确认网络的状况,以决定之后到底是回到慢启动还是拥塞避免。
快速恢复是一种中间状态,是因为只有一直收到冗余ACK才能维持这种状态:
- 当处于慢启动、拥塞恢复状态时,只有收到3个冗余ACK后,才会进入到 快速恢复状态。此时,窗口近似减半。
- 当处于快速恢复状态本身时,只有继续收到冗余ACK才能保持拥塞恢复状态。其他事件都会使得状态变化。注意,窗口还在继续“膨胀”。
处于快速恢复状态后,如果不再收到冗余ACK,那么其他事件将使得状态变化:
- 如果超时,说明网络已经拥塞。那么由快速恢复进入到慢启动。
- 如果收到new ACK,说明网络不拥塞,可以继续线性探索理想窗口大小。那么由快速恢复进入到拥塞避免。那么,窗口“收缩”到上一次窗口大小的一半值(即上面提到的“窗口近似减半”)。
总结
- 慢启动过程中,可能以数次的指数增长过程来找到一个
c
w
n
d
cwnd
cwnd值,但这个
c
w
n
d
cwnd
cwnd值可能太过保守。
- 拥塞避免过程中,以线性增长过程来增加
c
w
n
d
cwnd
cwnd值,以使得这个值既能相比慢启动结束时更大,也能保证整个网络不会拥塞。
- 快速恢复则是一个中间状态,当连续收到3个及以上冗余ACK时会进入。如果timeout,那么回到慢启动;如果收到new ACK,那么回到拥塞避免。
- 拥塞控制是加性增、乘性减的方式。
|