端到端的拥塞控制算法
简介
拥塞控制算法主要可以分为两个部分:在端系统使用的的源算法和在网络设备上使用的链路算法。其中端到端的网络拥塞控制算法通常根据接受到的ACK(Acknowledge character)确认包中包含的信息来调整拥塞控制窗口的大小,进而控制TCP连接的发送速率,譬如:TCP Tahoe, TCP Reno, TCP Vegas, TCP NewReno, TCP BIC, TCP CBIC, BBR等算法;在网络中间设备上(路由器、防火墙、交换机等)的拥塞控制算法(AQM-Active Queue Management-主动队列管理)通常根据设备中的缓存队列长度信息对网络拥塞控制程度进行判断,并将拥塞控制信息显示或隐式地告知端点,端点根据获得的拥塞控制信息对自身发送速率进行调整,譬如:FIFO, RED(Random Early Detection), ECN(Explicit Congestion Notification), FQ(Fair Queuing)等算法。
本文主要针对端对端的TCP拥塞控制算法进行分析。
为何拥塞?
网络产生拥塞的根本原因在于用户(或称端系统)提供给网络的负载大于网络资源容量和处理能力,即“需求”大于"供应".一般表现为数据时延增加、丢包概率增大、上层应用系统性能下降。如下图显示了拥塞发生的情况.
拥塞总是发生在网络资源中资源“相对”短缺的位置。拥塞发生位置的不均衡反映了\(Internet\)不均衡性。
1.首先是资源分布的不均衡性。下图中的带宽分布是不均衡的,当以 \(1Mb/s\) 的速率从S向D发送数据时,在网关R会发生拥塞。
2.其次是流量分布的不均衡。下图中带宽分布是均衡的,当 \(A\) 和 \(B\) 都以 \(1Mb/s\) 的速率向 \(C\) 发送数据时,在网关 \(R\) 也会发生拥塞。
拥塞产生的直接原因有如下3点:
- 存储空间不足,具体为网络中间设备缓冲区大小有限。在网络中,若待转发数据到达网络中间设备的速度大于网络中间设备转发数据的速度,待转发数据便会在网络中间设备的缓冲区内形成队列。如果缓冲区内没有足够的存储空间,随后收到的数据将会被丢弃,导致数据丢失。虽然增加网络中间设备的缓冲区大小可以缓解这一问题,但过大的缓冲区会导致数据的排队时间过长,增加网络时延,导致端点因数据交付超时而重新发送数据,反而加剧了网络的拥塞程度。
- 带宽容量不足。网络中的部分链路的速度较小,数据的传输速度较慢,远小于通过该链路数据所要求的传输速度,造成数据积压或丢失,导致网络拥塞。这样的低速链路通常称为瓶颈链路,局域网与广域网之间的链路就是常见的瓶颈链路。根据香农信息理论,任何信道带宽最大值即信道容量 $C=B\log _2\left( 1+\dfrac{S}{N} \right) \((\)N\(为信道白噪声的平均功率,\)S\(为信源的平均功率,\)B$为信道带宽).所有信源发送的速率 \(R\) 必须小于或等于信道容量 \(C\) .如果 \(R\) > \(C\) , 则在理论上无差错传输就是不可能的,所以在网络低速链路处就会形成带宽瓶颈,因而网络发生拥塞。
- 处理器处理能力弱、速度慢也能引起拥塞 . 如果路由器的 \(CPU\) 在执行排队缓存 ,更新路由表等功能时 ,处理速度跟不上高速链路 ,也会产生拥塞.同样 ,低速链路对高速 \(C PU\)也会产生拥塞.
TCP拥塞控制的主要过程
TCP 拥塞控制一般包含如下四个控制过程:慢启动 (slow start)、拥塞避免 (congestion avoidance)、快速重传 (fast retransmit) 和快速恢复 (fast recovery)。如图2.1 和图 2.2所示
慢启动(slow start)
早期的TCP在建立连接后,发送方将向网络发送多个数据包直到达到接受方声明的最大窗口为止。当两台主机在同一个局域网时,这种方式完全可行。但如果发送方和接收方之间存在路由器和低速链路时,这种方式就会产生一些问题。一些路由器可能需要排列数据包因而产生缓存容量不足的问题,从而引起数据包丢失,导致传输超时,降低网络的吞吐量(throughput)。
用来避免上述情况的算法称为慢启动算法:当建立新的TCP连接时,拥塞窗口(\(cwnd-congestion \ window\))初始化为一个数据包大小。发送方首先发送1个数据包,然后等待反馈回来的该数据包的 \(ACK\) 确认包,当接受到相应的 \(ACK\) 后,拥塞窗口的值从1增加到2,此时,发送方一次就能发送2个数据包。当这个两个数据包的 \(ACK\) 都到达后,拥塞窗口的值就增加至4.依次类推,每经过一个 \(RTT\)(\(round-trip \ time\)) 的时间,cwnd的值将增加一倍。这种方式使得拥塞窗口的增长将随着 \(RTT\) 呈指数级 (\(exponential\)) 增长。发送方向网络中发送的数据量将急剧增加。
拥塞避免阶段(congestion avoidance)
拥塞避免算法的作用是通过减缓 \(cwnd\) 值的增加速率推迟网络拥塞控制的发生,这样能够使得发送端在较长的一段时间内保持较高的数据传输率。该算法假设基于损坏而丢失包的比例很小(远小于\(1\%\))。因此,当出现丢包时,就表明源端和接收端之间的网络的某个地方可能出现了拥塞。包的丢失有两种暗示:定时器超时和接收到重复 \(ACK\)。
如果在慢启动阶段中,\(cwnd\) 增加至大于阈值 \(ssthresh\)(\(slow \ start \ threshold\)) 时,进入拥塞避免阶段,在每个 \(RTT\) 时间内,若发送方收到对当前 \(cwnd\) 对应数据的应答后,\(cwnd\) 就比原来增加一个最大\(TCP\)数据包所对应的字节数,即 \(cwnd\) 线性增长。
快速重传(fast retransmit)
无论是慢启动还是拥塞避免,\(cwnd\) 总是增长,使得负载逐渐增大最终导致网络拥塞。一旦数据包丢失,发送方只能在定时器超时之后才启动数据重发过程,这样可能会导致网络资源的浪费或闲置。因此快速重传的机制是,当发生丢包事件时,接收方将返回多个对相同数据的重复应答,若发送方连续收到三个以上的 \(ACK\) ,则表明很可能一个数据包丢失了。此时,发送方不必等重传定时器超时就重新传送那个可能丢失的数据包。TCP发送方利用快速重传算法检测和恢复数据包丢失。
快速恢复(fast recovery)
在快速重传可能丢失的数据包之后,\(TCP\) 就执行拥塞避免算法(而不是慢启动算法),这就是快速恢复算法。此后,快速恢复算法将控制新数据的传送,直到收到一个非重复的 \(ACK\) 为止,以后开始执行拥塞避免而不是慢启动。快速恢复其实是基于 "管道" 模型的 "报文守恒"(\(packet \ conservation\)) 的原则,控制的目标是使得网络中报文的个数保持恒定,只有当 "旧" 报文离开网络后,才能发送 "新" 报文进入网络。
快速恢复可使大窗口下发生中度拥塞时依旧具有较高的吞吐量。对于没有执行慢启动的原因是:收到重复的 \(ACK\) 意味着数据包已经丢失,而由于接收方收到另一个数据包时才会产生重复的 \(ACK\) ,说明有另外的数据包已经到达了接收方的缓存区里。也就是说,在这两个端点之间还存在数据发送,\(TCP\) 不想进入慢启动而造成流量的突然降低。
拥塞控制算法分析
TCP-Tahoe
\(TCP-Tahoe\) 网络拥塞控制算法是最早的端到端的拥塞控制算法。算法仅作用于端点,嵌入在 TCP 协议中,由 \(Jacobson\) 在 1988 提出,此算法极大的提高了网络控制拥塞的能力,并成为 \(Internet\) 标准之一。
\(TCP-Tahoe\) 算法通过改变拥塞窗口的大小来调节 \(TCP\) 连接中端点的数据发送速率,使得端点具有了主动控制数据发送速率,避免网络拥塞的能力。该算法中对拥塞窗口的调节主要包括了慢启动、快速重传和拥塞避免三种策略。
流程:首先使用慢启动策略,将拥塞控制窗口大小设为1个 \(MSS\)(\(Maximum \ Segment \ size\), 最大报文长度),随后每收到一个ACK确认包,拥塞窗口大小便增加1个 \(MSS\)。当端点收到3个连续的 \(ACK\) 确认包或发生重传超时 (\(Retransmission \ Timeout, RTO\)) 时,则认为传输过程中出现了数据包丢失,立即使用快速重传策略以重新传输丢失的数据包,重传结束后,将慢启动阈值 \(ssthresh\) 设置为当前拥塞窗口大小的一半,随后重新使用慢启动策略控制拥塞窗口大小,将其重置为1个 \(MSS\) 大小,当拥塞窗口大小增加至大于 \(ssthresh\) 时,则切换为拥塞避免策略。拥塞避免策略使拥塞窗口大小在 \(RTT\) 内增加一个 \(MSS\),直至再次检测到数据包丢失后重新使用慢启动策略, 算法依照数据包丢失情况来判断链路是否出现拥塞,并据此对拥塞窗口大小进行调节。
优点:\(TCP-Tahoe\) 算法在慢启动阶段发送速率较低时能够以指数形式快速增长,快速占用可用带宽,在发送速率接近链路带宽上限时在拥塞避免阶段以线性形式平稳增长,保持相对稳定。一方面可以探测出所处链路的最大带宽,另一方面能够使得链路上存在的其他 \(TCP\) 连接出现拥塞,使其降低自身数据发送速率,让出一部分网络资源提供给新建立的 \(TCP\) 连接使用。同时 \(TCP-Tahoe\) 的快速重传策略也解决了 \(TCP\) 连接超时重传导致的大量网络带宽的浪费。
缺点: 在 \(TCP-Tahoe\) 中,一旦检测到数据包丢失,拥塞窗口大小即被重置为1个 \(MSS\) ,这样 \(TCP\) 连接在数据包传输恢复正常之前无法传输新的数据,造成了链路带宽的大量浪费。
TCP-Reno
基于 \(TCP-Tahoe\) 算法中的缺陷,Jacobson 提出了 \(TCP-Reno\) 拥塞控 制算法,在 $TCP -Tahoe $原有的窗口调节策略中新增了快速恢复策略,这样 \(TCP\) 发送端能更准确地计算链路中的包数目。\(Reno\) 避免了网络拥塞不够严重时采用慢启动造成的过度减小发送窗口的现象。
流程:当发送方收到 3 个连续的重复 \(ACK\) 确认包时,并使用快速恢复策略来代替慢启动策略,将 \(ssthresh\) 设置为当前拥塞窗口大小的一半(但要大于或等于2个 \(MSS\)),随后将拥塞窗口大小调节为 \(ssthresh+3\times MSS\)(当收到3个重复 \(ACK\) 时,依旧采取拥塞避免阶段每次加 1 个 \(MSS\) ,所以变为 3倍 \(MSS\)),重传丢失的数据包,此后每收到1个依旧重复 \(ACK\) 确认包时,也发送一个新的数据包,直到收到非重复 \(ACK\) 确认包后,将拥塞窗口大小设置为 \(ssthresh\) 并使用拥塞避免策略调节拥塞窗口。
优点:\(TCP-Reno\) 算法更加灵活的利用了 “数据包守恒” 的思想,在其新增的快速恢复策略中,每收到一个 \(ACK\) 确认包即认为链路中出现了 1 个数据包大小空缺,则发送一个新的数据包,使得 \(TCP\) 连接在恢复正常传输之前也可以传输适量的数据,降低了超时重传发生的几率,加快了 \(TCP\) 连接从网络拥塞中恢复的速度。
缺点:\(TCP-Reno\) 的快速恢复策略是针对一个窗口仅丢失一个数据包的情况来设计的,其快速恢复策略仅对首个丢失的数据包进行重新传输,在收到非重复的 ACK 确认包 后,即认为 \(TCP\) 连接数据传输已经恢复正常,若出现一个窗口连续多个数据包丢失,在连续收到多个重复确认时,拥塞窗口大小会被多次减半,降低了 \(TCP\) 连接的链路带宽利用率。
TCP-NewReno
\(TCP-NewReno\) 对 \(Reno\) 中的 "快速恢复" 算法进行了补充。它考虑了一个发送窗口多个数据包丢失的情况。当发送方收到3个连续的重复 \(ACK\) 确认包时,将最大的已经发送的数据包的序号记为 \(recover\) ,在收到序号大于 \(recover\) 的 \(ACK\) 确认包后才切换为拥塞避免策略。
优点:\(TCP-NewReno\) 保证了 \(TCP\) 连接能够从网络拥塞中正确恢复,进一步的提高了算法的性能。
TCP-SACK
带选择性重传策略的 \(SACK\)(\(select \ Acknowledgment\)) 机制同样也可以克服 \(Reno\) 算法中一个窗口丢失多个数据包时,\(TCP\) 的性能可能变得很差的问题。\(SACK\) 选项域包含一些 \(SACK\) 块,每个 \(SACK\) 块报告已经收到或正在排队的非连续数据域。\(TCP\) 的接收端将 \(SACK\) 包传回给发送端通知它所收到的数据,这样发送端可选择仅重传丢失的数据。
优点:\(SACK\) 避免了不必要的重传,减少了时延,提高了网络吞吐量。
缺点:在中等规模或大规模的 \(BDP\) (\(bandwidth-delay \ product\)) 网络,此时的 \(SACK\) 就像 \(DOS\) 攻击一样,每次遍历都要消耗大量的 \(CPU\),时间复杂度为O(n^2),n为 \(packets \ in \ flight\) 的数量。\(SACK\) 算法最大的缺点还是在于修改协议 \(TCP\) 协议。
TCP-Vegas
无论是 \(TCP-Tahoe、TCP-Reno\) 还是 \(TCP-NewReno\) , 均为根据数据包的丢失情况推断网络拥塞程度的算法,这就导致 \(TCP\) 连接仅仅会在所处链路缓冲区无法容纳更多的数据包时才对自身发送速率进行控制,增加了网络的排队时延以及数据包丢失率。
\(TCP-Vegas\) 拥塞控制算法是根据最短 \(RTT\) 预测 \(TCP\) 连接的预期吞吐量 \(Excepted\) ,根据当前的 \(RTT\) 计算 \(TCP\) 的真实吞吐量 \(Actual\) ,并计算两者的差值,记为 \(diff\) ,若 \(diff\) 小于阈值 $\alpha $ ,则认为网络未发送拥塞,线性增加拥塞窗口大小;若 \(diff\) 超出阈值 $\beta $ ,则线性减小拥塞窗口大小;若 \(diff\) 处于两者之间,则拥塞窗口大小不变。
\[\text{期望吞吐量:}Expected=cwnd/BaseRTT \]\[\text{实际吞吐量:}Actual=cwnd/RTT \]\[\text{计算差值:}diff=\left( Expect-Actual \right) \times BaseRTT \]优点:由于 \(TCP-Vegas\) 并不完全依赖于数据包丢失情况来检测网络拥塞,而是主要采用 \(RTT\) 的改变来判断网络的可用带宽,因而对网络拥塞程度的判断较为灵敏,有效的降低了数据包丢失率,在一定程度上提高了网络吞吐量。
缺点:\(TCP-Vegas\) 存在较大的 \(TCP\) 公平性问题,在与其它算法共存的情况下,基于丢包的拥塞控制算法会尝试填满网络中的缓冲区,导致Vegas计算的 \(RTT\) 增大,进而降低拥塞窗口的大小,使得传输速度越来越慢,因此一直未被广泛采用。
HSTCP(Highspeed TCP)
上述 \(TCP-Tahoe、TCP-Reno\) 和 \(TCP-NewReno\) 算法,使用了加性增加乘性减小 (\(Additive \ Increase \ Multiplicative \ Decrease, AIMD\)) 的拥塞窗口大小调整机制,即每个 \(RTT\) 时间内拥塞窗口大小增长 \(\alpha\) (\(\alpha\) 的默认值为1),每当检测到数据包丢失时将拥塞窗口大小减小为 \(\beta\) 倍 (\(\beta < 1\) ,\(\beta\) 的值默认为0.5),会导致拥塞窗口大小需要经历很多个 \(RTT\) 才能使 \(TCP\) 连接的数据发送速度到达高带宽时延链路的承载上限,而一旦遇到数据包丢失则将拥塞窗口大小减半甚至重置为1个 \(MSS\) ,这使得拥塞控制窗口大小增长十分缓慢但减低过于迅速,浪费了大量的可用带宽。
我们可以粗略估计一下 \(reno\) 算法在高速TCP环境下的处理,在一个 \(RTT\) 为\(100ms\),带宽为\(10Gbps\),包大小为\(1500bytes\)的网络中,如果满速率发送 \(cwnd\ge \left( 10\times 1000\times 1000\times 1000 \right) bit/s\times 0.1s/\left( 8\times 1500 \right) bit=83333\)。假设 \(cwnd=83333\) ,如果快速重传 \(cwnd\) 减半后重新进入拥塞避免状态,那么大约需要 $83333/2\times 100=4166666ms=4166ms=69\min $ 通过简单计算我们可以发现,这种场景下大约需要69分钟,\(reno\) 的拥塞避免过程才能让 \(cwnd\) 足够大以充分利用 \(10Gbps\) 的带宽,显然69分钟的拥塞避免过程时间太长了,而且很可能会造成带宽浪费。实际上受限于丢包率,\(reno\) 的一个典型问题就是还没等 \(cwnd\) 增长到足够利用 \(10Gbps\) 的值就已经发生丢包并削减 \(cwnd\)了。
基于解决这一问题,\(Floyd\) 提出了 \(HSTCP\) 拥塞控制算法,其将拥塞窗口大小的函数 \(\alpha(cwnd)\) 和 \(\beta(cwnd)\) 作为 \(AIMD\) 机制中的参数 \(\alpha\) 和 \(\beta\) ,当拥塞窗口大小不超过预先设定的参数值 \(Low\_window\) 时,\(HSTCP\) 认为当前所处链路的时延带宽积较低,令 \(\alpha(cwnd)=1\) ,\(\beta(cwnd)=0.5\),与原有的拥塞控制窗口调节机制保持一致。(\(\alpha=a,\beta=b\))
当拥塞窗口大小大于 \(Low\_window\) 时,就会依据下列公式采取新的 \(a\) 和 \(b\) 来到达高时延带宽积的要求:
\[a\left( w \right) =w^2\times p\left( w \right) \times 2\times b\left( w \right) /\left( 2-b\left( w \right) \right) ,\ \text{其中}p\left( w \right) \text{时窗口为}w\text{时的丢包率} \]\[b\left( w \right) =\left( High\_Decrease-0.5 \right) \left( \log \left( w \right) -\log \left( W \right) \right) /\left( \log \left( W\_1 \right) -\log \left( W \right) \right) +0.5 \]\(High\_Decrease\) 是最大的乘性减小因子,标准TCP取值为0.5,\(HSTCP\) 取为0.1,\(W\) 为低窗口的临界值,也就是38,\(W\_1\)是窗口最大值,设为\(83000\)(为什么是\(83000\)?Floyd是通过10Gbps,100ms的网络下计算出来的窗口值,精确值是\(83333\))。关于 \(a\) 和 \(b\) 在不同拥塞窗口大小的取值,真正使用的时候可以通过查表的方式直接得出来。
\(HSTCP\) 同时也对慢启动策略指数增大拥塞窗口得方式进行了改进,在高\(BDP\) 网络上,原有的慢启动策略会以指数形式增加,越到后面拥塞窗口可以到达很大的值,从而导致 \(TCP\) 连接的传输速度增长至远超链路的缓冲区承载上限,造成大量数据包丢失。\(HSTCP\) 在拥塞窗口大小的指数增长过程中设置了一个阈值 \(max\_ssthresh\) ,当窗口大小小于 \(max\_ssthresh\) 时,仍以指数形式增长,一旦超过 \(max\_ssthresh\) 后,令其在 \(RTT\) 时间内的增长不得超过 \(max\_ssthresh/2\) ,修改后的机制被称作限制慢启动 (\(Limited \ Slow \ Start\))。
优点:\(HSTCP\) 算法能够在高速网络中,无论是在高丢包率或低丢包率的环境下,都能达到很高的吞吐量。
缺点:与传统 \(TCP\) 拥塞控制算法共存时会存在公平性的问题。当 \(TCP-Reno\) 和 \(HSTCP\) 共享链路时(高速网络中且数据包丢失率较小的情况下) 在拥塞发生后,\(TCP-Reno\) 使用的拥塞窗口增长速率远远慢于 \(HSTCP\) 的增长速率,因此,经过若干次拥塞控制事件后,\(HSTCP\) 会不断地侵占传统 \(TCP\) 的可用带宽。
TCP-BIC 与 TCP-CUBIC
关于 \(TCP-BIC\) 和 \(TCP-CUBIC\) 算法的详细内容,可以参考我的另外一篇博客,这里不再详述。
其中关于 \(TCP-BIC\) 算法公平性问题,如下图所示,由于 \(TCP-BIC\) 是在收到 \(ACK\) 确认包时对拥塞窗口进行调节的,这样在 \(RTT\) 不同的两个 \(TCP\) 连接竞争同一瓶颈链路时,\(RTT\) 较大的 \(TCP\) 连接由于收到的数据包频率始终小于 \(RTT\) 较小的 \(TCP\) 连接,其拥塞窗口 调节频率较低,增长较慢,导致 \(TCP-BIC\) 存在很严重的 RTT 公平性问题 。