← 返回 计算机网络原理

计算机网络原理

第三章 拥塞控制补充

1. 拥塞是什么?

太多数据需要网络传输,超过了网络承载能力。

注意这里说的是:

超过了网络的能力,不是超过某一台主机的能力。

比如一堆车都挤到高速匝道上,问题不是某辆车不行,而是路本身堵了。

1.2 拥塞的表现

拥塞主要有两个表现:

第一,分组排队时间变长

路由器有缓冲区,分组到得太快,就会在路由器队列里排队。

输入速率越大,delay的时间越长

所以:

λin{\lambda }_{in}↑

输入速率越大,队列越长,时延 delay 越大。

尤其当接近链路容量时,时延会急剧上升。

第二,分组丢失

如果路由器缓冲区满了,后来的分组就会被丢弃。

也就是:

队列满了,分组进不来,只能丢。

丢了之后 TCP 会重传,重传又会额外占用网络资源,于是可能更堵。

2. 拥塞控制和流量控制的区别

流量控制:防止发送方把接收方撑爆。

它关注的是:接收方来不来得及收?

对应窗口:

RcvWinRcvWin

拥塞控制:防止发送方把网络撑爆。

它关注的是:网络中间的路由器、链路能不能承受?

对应窗口:

cwndcwnd

TCP 实际发送窗口

TCP 发送方真正能发多少,由两者共同限制:

SendWin=min(cwnd,RcvWin)SendWin=min(cwnd,RcvWin)

所以:

RcvWin 管接收方能力

cwnd 管网络拥塞程度

真正发送窗口取较小值

3. 三个场景理解拥塞代价

为什么拥塞会降低吞吐量,甚至浪费网络资源。

场景 1:无限缓冲区,没有丢包

第三章 拥塞控制补充 配图 1

假设:

一个路由器

两条输入链路

一条输出链路

输出链路容量是 $R$

每个连接最多分到 $R/2$

如果输入速率 ${\lambda }_{in}$ 很小,输出也能跟着增加。

但当:

λinR/2{\lambda }_{in}\to R/2

吞吐量最多也只能到:

λout=R/2{\lambda }_{out}=R/2

再多发也没用,因为瓶颈链路就这么大。

这个场景的代价:因为缓冲区无限大,所以不会丢包。

但是会出现:队列越来越长,排队时延越来越大。

所以结论是:

吞吐量不可能超过瓶颈链路容量,接近容量时时延会急剧增加。

4. 场景 2:有限缓冲区,有丢包和重传

现实中路由器缓冲区不可能无限大。

所以:

第三章 拥塞控制补充 配图 2

队列满了,分组会被丢弃。

这时候 TCP 发送方如果发现分组丢了,就会重传。

这里有两个输入速率:

λin{\lambda }_{in}

表示原始数据输入速率。

λin{\lambda }_{in}^{'}

表示实际进入网络的总速率,包括:

原始数据 + 重传数据

所以:

λinλin{\lambda }_{in}^{'}\ge {\lambda }_{in}

4.1 理想情况:发送方知道什么时候会丢包

我们假设发送方特别聪明,知道路由器什么时候有空缓冲。

那它只在有空间时发送。

这种情况下:

不会发生无意义重传

网络利用率比较高

吞吐量可以随着 ${\lambda }_{in}$ 增加接近 $R/2$

但是这只是理想情况。

现实 TCP 不知道路由器缓冲区具体情况。

4.2 部分理想:发送方知道哪个分组丢了

如果分组丢了,发送方知道它丢了,只重传这个分组。

这时候比完全不知道要好。

但是仍然有代价:重传分组也要占用链路容量。

所以虽然接收端最后收到的是有效数据,但网络中传输了更多数据。

4.3 真实情况:发送方不知道是否真的需要重传

真实 TCP 只能靠:

超时

重复 ACK

来推测丢包。

问题是:

有时候分组没有真的丢,只是 ACK 来晚了。

发送方太早超时,就会重传一个副本。

如果原来的分组和重传分组最后都到了接收方,那么其中一个就是没必要的。

这叫:没有必要的重传。

场景 2 的结论

拥塞的代价有两个:

第一,有必要重传会浪费链路资源

因为丢包之后,要重新发一遍。

比如一个分组本来经过 3 条链路,结果最后一跳前丢了。

那前面几条链路的传输资源已经被浪费了。

第二,没必要重传会进一步浪费资源

原始分组没丢,只是 ACK 慢了。

发送方以为丢了,又发一份。

结果两份都到了。

接收方只需要一份,多出来那份就是浪费。

5. 场景 3:多跳路径中的拥塞

这块图里有多个发送端、多条路径。

第三章 拥塞控制补充 配图 3第三章 拥塞控制补充 配图 4

关键点是:

某个下游路由器把分组丢了,那么这个分组在上游走过的链路资源全部白费。

举个例子:

一个分组路径是:

AR1R2R3BA\to {R}_{1}\to {R}_{2}\to {R}_{3}\to B

如果它在 ${R}_{3}$ 被丢弃,那它在 ${R}_{1},{R}_{2}$ 消耗过的传输资源都浪费了。

所以拥塞不仅影响当前链路,还会浪费整条路径上游资源。

第三章 拥塞控制补充 配图 5

6. 拥塞控制方法

6.1 端到端拥塞控制

端系统自己判断网络是否拥塞。

第三章 拥塞控制补充 配图 6

网络中间的路由器不直接告诉主机:堵了。

TCP 主要根据:

ACK 是否正常回来

是否出现重复 ACK

是否超时

来判断拥塞。

传统 TCP 用的就是这种方法。

6.2 网络辅助拥塞控制

路由器参与反馈。

第三章 拥塞控制补充 配图 7

也就是路由器发现拥塞后,显式告诉发送方:你慢点发。

比如:

ECN

ATM

DECbit

但经典 TCP 拥塞控制主要还是端到端方式。

7. TCP 拥塞控制的三个核心问题

第三章 拥塞控制补充 配图 8第三章 拥塞控制补充 配图 9

问题 1:如何检测拥塞?

TCP 没有直接看到路由器队列。

它靠事件推测:

第一,3 个冗余 ACK

说明可能有分组丢了。

但是网络还能继续传,因为后面的分组到达了接收方,接收方才会不断发重复 ACK。

所以这一般认为是:轻微拥塞。

第二,超时 Timeout

说明 ACK 很久没回来。

可能网络堵得比较严重,或者分组真的丢了。

所以 TCP 通常认为:

超时比 3 个冗余 ACK 更严重。

问题 2:如何控制发送速率?

TCP 用拥塞窗口控制发送速率:

cwndcwnd

大致发送速率为:

ratecwndRTTrate\approx \frac{cwnd}{RTT}

所以:

cwnd 越大,发送越快

RTT 越大,单位时间能发出去的数据越少

问题 3:控制策略是什么?

总体思想:

没堵的时候慢慢加速,堵了之后赶紧降速。

具体就是:

没拥塞:增大 cwnd

轻微拥塞:适当减小 cwnd

严重拥塞:大幅减小 cwnd

8. TCP 拥塞窗口 cwnd

cwnd 是发送方维护的窗口。

它表示:发送方最多允许有多少未确认的数据在网络中。

也就是:

LastByteSentLastByteAckedcwndLastByteSent-LastByteAcked\le cwnd

这里的未确认数据就是:已发送,但是还没收到 ACK 的数据。

第三章 拥塞控制补充 配图 10

9. 慢启动 Slow Start

名字叫慢启动,但其实增长很快。

9.1 什么时候用?

连接刚开始时,TCP 不知道网络能承受多大速率。

所以先从很小的窗口开始试探。

一般:

cwnd=1MSScwnd=1MSS

9.2 怎么增长?

每收到一个 ACK:

cwnd=cwnd+1MSScwnd=cwnd+1MSS

从 RTT 角度看,每个 RTT 大约翻倍。

所以慢启动是:指数增长。

比如:

1,2,4,8,16,1,2,4,8,16,…

9.3 慢启动什么时候停止?

当:

cwndssthreshcwnd\ge ssthresh

就从慢启动切换到拥塞避免。

ssthresh 叫慢启动阈值。

可以理解为:再指数增长就危险了,后面改成线性增长。

10. 拥塞避免 Congestion Avoidance

拥塞避免阶段不再指数增长。

它采用线性增长:

cwnd=cwnd+MSScwndcwnd=cwnd+\frac{MSS}{cwnd}

每收到一个 ACK,加一点点。

从 RTT 角度看,大约:

每个RTT增加1MSS\text{每个}RTT\text{增加}1MSS

所以拥塞避免阶段是:线性增长。

第三章 拥塞控制补充 配图 11

图像上就是斜着往上走。

11. AIMD:加性增,乘性减

AIMD 是 TCP Reno 的核心思想。

全称:Additive Increase Multipative Decrease

11.1 Additive Increase

没有发生拥塞时:

cwndcwnd

线性增加。

也就是每个 RTT 大约增加 1 MSS。

11.2 Multiplicative Decrease

发生拥塞时:

cwndcwnd

乘性减少,一般减半。

也就是:

ssthresh=cwnd2ssthresh=\frac{cwnd}{2}

11.3 为什么是 AIMD?

因为它比较公平、稳定。

多个 TCP 流竞争同一条瓶颈链路时,AIMD 会让它们逐渐接近公平分配。

如果不堵就线性加,堵了就乘性减。

12. 遇到丢包时 TCP 怎么处理?

12.1 超时 Timeout

超时说明可能是严重拥塞。

处理方式:

ssthresh=cwnd2cwnd=1MSSssthresh=\frac{cwnd}{2}cwnd=1MSS

然后进入慢启动。

也就是:超时:窗口降到 1,从头慢启动。

12.2 3 个重复 ACK

3 个重复 ACK 一般说明轻微拥塞。

因为后面的分组还能到达接收方,说明网络还没完全堵死。

处理方式:

ssthresh=cwnd2cwnd=ssthresh+3MSSssthresh=\frac{cwnd}{2}cwnd=ssthresh+3MSS

然后进入快速恢复,或者保持在拥塞避免附近。

3 个重复 ACK:窗口大约减半,不一定回到 1。

13. TCP Tahoe 和 TCP Reno 区别

TCP Tahoe

不管是:

超时

3 个重复 ACK

都认为拥塞比较严重。

所以:

cwnd=1MSScwnd=1MSS

重新慢启动。

TCP Reno

区分两种拥塞:

超时

严重拥塞:

cwnd=1MSScwnd=1MSS

重新慢启动。

3 个重复 ACK

轻微拥塞:

cwnd=cwnd2cwnd=\frac{cwnd}{2}

然后快速恢复。

所以 Reno 比 Tahoe 更温和,吞吐量更好。

14. ssthresh 怎么更新?

ssthresh 的作用是:

慢启动和拥塞避免的分界线。

一旦检测到丢包事件:

ssthresh=当前cwnd2ssthresh=\frac{\text{当前}cwnd}{2}

然后:

如果是超时:cwnd = 1 MSS

如果是 3 个重复 ACK:cwnd 大约减半

15. TCP 控制策略

事件 | 说明 | cwnd 变化 | 进入阶段 正常 ACK,且 cwnd < ssthresh | 网络暂时没堵 | 指数增长 | 慢启动 SS 正常 ACK,且 cwnd ≥ ssthresh | 接近拥塞点 | 线性增长 | 拥塞避免 CA 3 个重复 ACK | 轻微拥塞 | cwnd 大约减半 | 快速恢复 / CA 超时 | 严重拥塞 | cwnd = 1 MSS | 慢启动 SS

16. CUBIC 是什么?

传统 TCP Reno 是线性增长,效率比较低。

在高速长距离网络中,比如:

带宽很大

RTT 很大

线性增长太慢,不能充分利用带宽。

所以 Linux 默认常用 CUBIC。

16.1 CUBIC 的思想

CUBIC 不再简单用线性增长。

它使用三次函数控制窗口增长:

W(t)=C(tK)3+WmaxW(t)=C(t-K{)}^{3}+{W}_{max}

16.2 CUBIC 图像怎么理解?

设上一次丢包前的最大窗口是:

Wmax{W}_{max}

丢包后,窗口降低。

然后 CUBIC 试图重新接近 ${W}_{max}$ 。

它的增长特点是:

离 ${𝒔𝒕}_{}$ 远时,增长快

因为之前到过那里,说明网络可能还能承受。

接近 ${𝒔𝒕}_{}$ 时,增长慢

因为上次就是在附近发生丢包。

所以接近这个点时要谨慎。

超过 ${𝒔𝒕}_{}$ 后,再继续探测

如果一直没丢包,说明网络可能变好了,于是继续增加窗口。

16.3 CUBIC 和 Reno 对比

Reno:

线性增加,锯齿形比较规则。

CUBIC:

快速接近历史最大窗口,靠近时放慢,然后再探测更高带宽。

所以 CUBIC 在高速网络里通常比 Reno 更能充分利用带宽。

TCP 会不断增加发送速率,直到某条链路先顶不住,这条链路就是瓶颈链路。

瓶颈链路的特点:

容量最小,或者负载最大

最容易发生排队

最容易丢包

决定整条路径的吞吐量上限

所以拥塞控制本质是在探测:

我的发送速率能不能把瓶颈链路打满,但又不要把它打爆。

问法 1:拥塞控制和流量控制区别

答:

流量控制防止发送方压垮接收方,由接收窗口 RcvWin 控制;拥塞控制防止发送方压垮网络,由拥塞窗口 cwnd 控制。TCP 实际发送窗口为 $min(cwnd,RcvWin)$ 。

问法 2:为什么接近链路容量时时延急剧增加?

答:

因为输入速率接近输出链路容量时,路由器队列中的分组越来越多,排队时间迅速增加。即使吞吐量不能继续超过链路容量,时延仍会急剧上升。

问法 3:为什么重传会降低有效吞吐量?

答:

重传分组占用了链路容量,但不一定增加新的有效数据交付。尤其是没必要的重传,会让网络传输多个副本,进一步浪费带宽,使有效吞吐量下降。

问法 4:TCP 如何检测拥塞?

答:

TCP 主要通过丢包事件推测拥塞,包括超时和 3 个重复 ACK。超时通常表示严重拥塞,3 个重复 ACK 通常表示轻微拥塞。

问法 5:慢启动和拥塞避免区别

答:

慢启动从 cwnd = 1 MSS 开始,每个 RTT 近似翻倍,是指数增长;当 cwnd 达到 ssthresh 后进入拥塞避免,每个 RTT 约增加 1 MSS,是线性增长。

问法 6:超时和 3 个重复 ACK 后 cwnd 怎么变?

答:

超时:ssthresh = cwnd / 2,cwnd = 1 MSS,重新慢启动。 3 个重复 ACK:ssthresh = cwnd / 2,cwnd 大约减半,进入快速恢复或拥塞避免。

发送方不知道网络到底堵没堵,所以 TCP 用 ACK、重复 ACK、超时来推测拥塞,然后用 cwnd 控制发送速率。

核心公式:

SendWin=min(cwnd,RcvWin)ratecwndRTTSendWin=min(cwnd,RcvWin)rate\approx \frac{cwnd}{RTT}

核心机制:

正常 ACK:增大 cwnd

慢启动:指数增长

拥塞避免:线性增长

3 个重复 ACK:轻微拥塞,cwnd 减半

超时:严重拥塞,cwnd 降为 1 MSS

ssthresh:慢启动和拥塞避免的分界

AIMD:不堵线性加,堵了乘性减

CUBIC:高速网络中用三次函数更快探测带宽

TCP 拥塞控制的目标不是让链路空着,而是让瓶颈链路尽量满,但不要溢出。