计算机网络经典问题透视:简述一下TCP拥塞控制算法中的拥塞避免算法

引言:为什么需要拥塞避免?

在TCP的拥塞控制四部曲------慢启动(Slow Start)、拥塞避免(Congestion Avoidance)、快速重传(Fast Retransmit)和快速恢复(Fast Recovery)中,拥塞避免扮演着至关重要的角色 。当TCP连接通过慢启动阶段快速探测到网络可用带宽,并将拥塞窗口(cwnd)提升至慢启动阈值(ssthresh)后,就必须进入一个更加审慎和精细的控制阶段,以防止因窗口增长过快而压垮网络,这就是拥塞避免阶段的使命 。

其核心目标是在不确定网络瓶颈带宽具体数值的情况下,以一种可持续的方式,缓慢地增加数据发送量,尽可能多地利用网络资源,同时避免或延迟拥塞的发生。可以说,一个设计优良的拥塞避免算法,是在"贪婪"地追求高吞吐量与"保守"地维持网络稳定之间寻找最佳平衡点的艺术。

一、 经典的拥塞避免算法:AIMD模型

传统的TCP拥塞避免算法,如TCP Reno所采用的,是基于"加性增,乘性减"(Additive Increase, Multiplicative Decrease, AIMD)模型构建的 。这个模型是拥塞控制领域的基石,其工作原理简洁而有效:

  1. 加性增(Additive Increase, AI):

    • 触发条件: 当TCP连接处于拥塞避免阶段,并且成功收到新的ACK确认时。
    • 窗口调整: cwnd会进行线性增长。一个经典且简化的实现是,每收到一个ACK,cwnd增加 1/cwnd 个MSS(最大报文段长度)。这等效于在一个RTT(往返时间)内,cwnd大约增加1个MSS 。这种缓慢的"爬坡"方式,可以小心翼翼地探测网络中是否还有额外的可用带宽。
    • Linux内核实现: 在Linux内核的早期实现中,如Reno算法,这一逻辑通过tcp_reno_cong_avoid函数来承载。该函数会检查当前是否处于拥塞避免阶段,并根据收到的ACK数量来线性增加拥塞窗口 。这个过程通常与一个通用的拥塞避免处理函数 tcp_cong_avoid 协同工作,后者提供了慢启动和拥塞避免阶段增长的基础框架 。
  2. 乘性减(Multiplicative Decrease, MD):

    • 触发条件: 当TCP检测到网络拥塞时。拥塞的信号通常是丢包,例如通过RTO(Retransmission Timeout)超时或收到三个重复的ACK(触发快速重传)来判断。
    • 窗口调整: cwnd会被大幅度削减。通常,慢启动阈值ssthresh会被设置为当前cwnd的一半(ssthresh = cwnd / 2),然后cwnd被重置。在RTO超时的情况下,cwnd通常被重置为1个MSS,重新进入慢启动阶段;在快速恢复场景下,cwnd也会被设置为新的ssthresh值 。这种"断崖式"下降是为了快速缓解网络压力,给路由器缓存留出恢复空间。

AIMD的核心思想是:好消息(收到ACK)要谨慎乐观,坏消息(发生丢包)要迅速悲观。这种不对称的调整策略被证明在分布式环境下能够有效地收敛到一个公平和高效的状态。

二、 AIMD的现代演进:CUBIC算法

随着网络带宽的飞速发展,特别是长肥网络(Long Fat Networks, LFNs)的出现,传统AIMD的线性增长模式在高带宽、高延迟环境下显得力不从心。在一个RTT内仅增加1个MSS,对于一个拥有Gbps级别带宽和100ms延迟的链路来说,需要非常长的时间才能将带宽占满,这导致了严重的带宽资源浪费。

为了解决这个问题,CUBIC应运而生,并成为了Linux内核中默认的TCP拥塞控制算法 。CUBIC本质上仍然是基于丢包反馈的AIMD框架,但它对"加性增"部分进行了重大革新 。

  • 核心思想: CUBIC的窗口增长函数不再是简单的线性函数,而是一个以丢包事件发生点为拐点的 **三次函数(立方函数)**‍ 。

    • cwnd刚刚从上一次丢包事件中恢复(即cwnd远小于上次丢包时的窗口值W_max)时,它会像一个凹函数一样快速增长,迅速恢复到接近W_max的水平。
    • cwnd接近W_max时,增长速度会变得非常平缓,进入一个"平台期",小心地探测瓶颈。
    • cwnd超过W_max后,它会再次以凸函数的形式加速增长,积极地探测新的可用带宽。
  • 窗口更新公式: CUBIC的窗口增长由以下公式描述:

    其中,是自上次丢包以来的时间,是上次丢包时的窗口大小,是一个常数,而则是窗口增长到所需的时间 。

  • 优势: 这种设计使得CUBIC的窗口增长与RTT无关,具有RTT公平性。无论RTT长短,CUBIC的窗口增长曲线是相同的,这解决了传统TCP Reno中短RTT连接比长RTT连接更具"侵略性"的问题。同时,其在接近饱和点时的审慎和超越饱和点后的积极探测,使其能够更好地适应高速网络 。

  • Linux内核实现: CUBIC的实现位于内核源码的 net/ipv4/tcp_cubic.c 文件中。其拥塞避免逻辑主要由 bictcp_cong_avoid (在早期BIC算法基础上发展而来) 或类似的函数实现,该函数会在每个ACK到达时,根据上述立方函数计算出当前时间点应该达到的目标cwnd,并进行更新 。

三、 颠覆传统:基于模型的拥塞避免算法BBR

尽管CUBIC等算法极大地优化了AIMD模型,但它们都有一个共同的根基:基于丢包。这类算法的核心假设是"丢包等同于拥塞"。然而,在现代网络中,这个假设越来越站不住脚:

  • 浅缓存路由器: 在一些数据中心网络中,路由器的缓冲区非常小,轻微的流量突发就可能导致丢包,而此时网络并未真正达到拥塞状态。
  • 无线网络和卫星链路: 在这些网络中,信号干扰、切换等非拥塞因素也会导致大量的随机丢包 。

基于丢包的算法在这些场景下会频繁地、不必要地降低发送速率,导致吞吐量低下 。更糟糕的是,在缓冲区较大的网络(Bufferbloat)中,它们又倾向于填满整个缓冲区才会触发丢包,导致极高的网络延迟。

为了从根本上解决这些问题,Google提出了 BBR(Bottleneck Bandwidth and Round-trip propagation time) ‍ 拥塞控制算法,它开创了基于模型的拥塞控制新范式 。

  • 核心思想: BBR不再关注丢包,而是直接对网络的两个关键物理参数进行建模和测量:
    1. 瓶颈带宽(Bottleneck Bandwidth, BtlBw): 网络路径上容量最小的链路的速率。
    2. 往返传播时延(Round-trip Propagation Time, RTTprop): 数据包在路径上传播的固有延迟,不包括排队延迟。

BBR认为,网络达到最佳工作状态的点,是当飞行中的数据量(inflight data)恰好等于 带宽时延积(Bandwidth-Delay Product, BDP) ‍ 时,即 inflight = BtlBw * RTTprop。此时,吞吐量最大化,同时排队延迟最小化 。

  • 拥塞避免阶段的"探测"周期: BBR没有传统意义上的"拥塞避免"线性增长阶段。取而代之的是一个周期性的探测循环,主要在 PROBE_BW 状态下进行 :
    1. 探测带宽(PROBE_BW): BBR会以一个略高于当前估计带宽(例如1.25 * BtlBw)的速率发送数据,持续一小段时间,以探测瓶颈带宽是否有所增加。这可能会短暂地在瓶颈处形成一个小队列。
    2. 排空队列(DRAIN): 接着,BBR会以一个略低于估计带宽(例如0.75 * BtlBw)的速率发送数据,以排空上一步中可能产生的队列,降低延迟。
    3. 巡航(Cruise): 在大部分时间里,BBR会以等于估计带宽(1.0 * BtlBw)的速率发送数据,保持网络的高效利用。

这个由8个RTT组成的增益周期(Pacing Gain Cycle)不断循环,使得BBR能够动态追踪网络带宽的变化,而不是等待丢包信号 。

  • 性能表现: 在高丢包、高延迟或存在缓冲区膨胀的网络中,BBR通常能获得比CUBIC更高的吞吐量和显著更低的延迟 。因为它不把随机丢包误判为拥塞,也主动避免填满网络缓冲区。

  • Linux内核实现: 自Linux 4.9内核起,BBR被正式合入主线 。其核心逻辑在 net/ipv4/tcp_bbr.c 中实现。主函数 bbr_main 负责根据当前所处的状态(如BBR_PROBE_BW)来设置发送速率(pacing rate)和拥塞窗口 。与传统算法不同,BBR强依赖于内核的fq(Fair Queue)排队规则来精确控制数据包的发送时机(pacing),从而实现对发送速率的精准控制 。

四、 数据中心场景的特化:DCTCP

在延迟极低、带宽极高且网络设备可控的数据中心(Data Center)环境中,拥塞控制算法可以有更激进和精细的优化。**DCTCP(Data Center TCP)**‍ 就是为此类场景设计的杰出代表 。

  • 核心思想: DCTCP利用了现代交换机普遍支持的 **显式拥塞通知(Explicit Congestion Notification, ECN)**‍ 功能。它不再等待丢包,而是通过交换机对数据包进行ECN标记来获取早期、细粒度的拥塞信号。

    • 交换机设置一个非常低的队列长度阈值(K)。当队列长度超过K时,到达的数据包就会被标记上"拥塞经历"(Congestion Experienced, CE)位。
    • TCP接收方看到CE标记后,在返回的ACK中设置ECN-Echo标志。
    • TCP发送方根据收到的ECN-Echo ACK的比例,来精确估算在过去一个RTT内发生拥塞的数据包比例(α)。
  • 拥塞避免策略: DCTCP的拥塞避免策略非常直接:它不像传统TCP那样在检测到拥塞后将窗口减半,而是根据拥塞程度(α)进行成比例的缩减
    cwnd = cwnd * (1 - α/2)

    这种平滑的、与拥塞程度相关的窗口调整方式,使得DCTCP能够维持非常短的队列,从而实现近乎为零的排队延迟,同时保持高吞吐量 。

  • 部署与调优: DCTCP的成功部署强依赖于整个数据中心网络(交换机)对ECN和相应标记阈值的正确配置 。它是一种端到端和网络设备协同工作的拥塞控制方案,非常适合于环境可控的私有云和数据中心内部网络。

五、 算法对比与未来展望

算法 核心机制 拥塞信号 拥塞避免阶段行为 适用场景
Reno (AIMD) 基于丢包 丢包 (超时/3-dup ACK) 线性增加,每个RTT增大约1 MSS 通用互联网(已显老旧)
CUBIC 基于丢包 丢包 立方函数增长,与RTT无关 高速、长延迟网络(当前主流)
BBR 基于模型 带宽和RTT测量 周期性探测带宽和排空队列 高丢包、延迟抖动、缓冲区膨胀网络
DCTCP 基于ECN 交换机ECN标记 根据拥塞比例平滑减少窗口 低延迟数据中心网络

未来展望:

拥塞控制算法的演进从未停止。BBR的出现引发了学术界和工业界对基于模型算法的极大兴趣,其后续版本BBRv2、BBRv3等正在致力于解决初代BBR在公平性、带宽探测收敛速度等方面的问题 。同时,随着机器学习和人工智能技术的发展,基于AI的智能拥塞控制算法(如PCC Vivace)也开始崭露头角,它们试图通过实时学习网络状态来动态调整拥塞控制策略,以适应更加复杂多变的网络环境 。

结论

TCP拥塞避免算法从最初简单的AIMD模型,发展到适应高速网络的CUBIC,再到颠覆传统的基于模型的BBR,以及面向特定场景高度优化的DCTCP,其演进之路清晰地反映了我们对网络拥塞本质理解的不断深化,以及为应对日益增长的网络性能需求所做出的不懈努力。作为网络开发者和运维工程师,理解不同拥塞避免算法的设计哲学、工作原理和适用场景,对于诊断网络问题、优化应用性能、构建稳定高效的现代网络服务至关重要。未来,我们有理由相信,更加智能、自适应的拥塞控制算法将继续涌现,为全球互联网的持续发展提供坚实的基础。

相关推荐
BingoGo1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
Sinclair3 天前
简单几步,安卓手机秒变服务器,安装 CMS 程序
android·服务器
JaguarJack3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
Rockbean4 天前
用40行代码搭建自己的无服务器OCR
服务器·python·deepseek
茶杯梦轩4 天前
CompletableFuture 在 项目实战 中 创建异步任务 的核心优势及使用场景
服务器·后端·面试
JaguarJack4 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel