TCP的拥塞控制算法中的PRR(Proportional Rate Reduction)机制详解
一、 问题/知识点描述
PRR是一种在TCP快速恢复阶段,用于精确控制数据发送速率,以更快、更平稳地从丢包中恢复的算法。它于2011年被提出,并被集成到Linux内核的TCP实现中,旨在替代传统快速恢复中较为粗糙的速率控制方法。
核心问题:传统的快速恢复算法(如Reno、NewReno)在收到部分ACK(例如,触发快速重传的3个重复ACK)后,会将拥塞窗口(cwnd)减半,并进入恢复阶段。在这个阶段,发送端每收到一个ACK,就可以发送新数据。这可能导致发送速率突然下降后又突然飙升(因为cwnd减半但实际可发送量可能瞬时较多),造成发送“突发”(burst),加剧网络拥塞或导致不必要的丢包。PRR的目标是让发送速率在恢复阶段平滑地、按比例地降低到新的、减半后的cwnd值,并保持在这个水平,直到恢复结束。
二、 循序渐进的讲解
步骤1:场景设定与基础回顾
- 假设TCP连接正在正常传输,其
cwnd(拥塞窗口)为20个报文段(MSS),ssthresh(慢启动阈值)为15。 - 发生了一个数据包丢失。接收端因为后续数据包的到达,会发送重复的ACK(Dup-ACK)。
- 发送端收到第3个Dup-ACK,触发快速重传。此时,按照经典算法(如NewReno):
ssthresh更新为:ssthresh = max(在外数据包数 (FlightSize) / 2, 2)。假设FlightSize为20,则ssthresh = 10。cwnd更新为:cwnd = ssthresh + 3(因为收到了3个Dup-ACK,相当于“信用”)。所以cwnd = 13。- 进入快速恢复阶段。
步骤2:传统快速恢复的发送行为(作为对比)
在快速恢复阶段,传统算法(如NewReno)的发送规则是:每收到一个ACK(无论是重复ACK还是新数据的ACK),就可以发送一个新数据包。
- 问题:当
cwnd从20骤降到13后,但FlightSize(在外数据包数)可能因为重传和新发送而迅速变化。这个规则可能导致发送端在恢复初期,因为收到多个Dup-ACK,连续发送多个数据包,形成一个小的“突发流”。虽然cwnd限制了总量,但发送节奏不均匀。
步骤3:PRR算法的核心思想
PRR的核心是:在快速恢复阶段,发送的数据总量应被精确控制,使得在恢复过程结束时,在外数据包数(FlightSize)恰好等于新的、减半后的拥塞窗口(ssthresh)。并且,这个发送过程应该是按比例均匀分布的。
- 目标:
prr_delivered(进入恢复阶段后成功递送的数据量)与prr_out(进入恢复阶段后发送的数据量)保持一个恒定的比例,直到恢复完成。 - 公式核心:它定义了在恢复期间,每确认一个旧数据包,允许发送多少新数据包。
步骤4:PRR算法的两个模式与关键变量
PRR定义了两个模式:PRR=SS(慢启动风格)和PRR=CRB(保守速率削减)。模式的选择基于一个关键变量:recover。
-
变量初始化(当触发快速重传,进入恢复阶段时):
prr_delivered= 0 (进入恢复后,累计确认交付的字节数)prr_out= 0 (进入恢复后,累计发送的字节数)ssthresh= 当前计算的新的慢启动阈值(如步骤1中的10)pipe= 当前估算的在外数据包数(一个动态值,每发送+1,每确认-1)。recover=snd.nxt(下一个要发送的序列号)。这是恢复阶段的“终点”标志。所有序列号小于recover的数据,都属于这次恢复要处理的范围。
-
模式选择(在每次决定能否发送数据时):
- 计算
prr_delivered自恢复开始以来的增量。 - 如果当前
snd.nxt(下一个要发送的序列号) <recover,说明我们还在重传或发送“旧”的、在恢复范围内的数据。此时使用 PRR=SS模式。 - 如果当前
snd.nxt>=recover,说明所有旧数据都已重传或发送完毕,开始发送全新的数据。此时使用 PRR=CRB模式。通常PRR=CRB允许更保守的发送速率。
- 计算
步骤5:PRR发送量计算(核心算法)
每当一个数据包被确认(ACK到达),算法计算本次可以发送多少数据:
- 更新
prr_delivered:增加刚被确认的字节数。 - 计算允许发送量:
- PRR=SS模式(慢启动风格):
limit = prr_delivered * ssthresh / RecoverFS - prr_out- 其中
RecoverFS是进入恢复瞬间的FlightSize(例子中的20)。 - 这个公式确保发送总量
prr_out与交付总量prr_delivered的比例为ssthresh/RecoverFS(例子中是10/20=0.5)。即每交付2个字节,只允许发送1个新字节,从而平滑地将管道中的数据量从20削减到10。
- PRR=CRB模式(保守速率削减):
limit = (prr_delivered - prr_out)加上一些微调。- 这个模式更严格,基本保证
prr_out不会超过prr_delivered太多,确保恢复后FlightSize稳定在ssthresh附近,不会增长。
- PRR=SS模式(慢启动风格):
- 发送决策:计算出的
limit表示当前允许发送的字节数“信用”。只有当limit > 0且当前pipe(估算在途数据)小于cwnd(注意,在PRR中,cwnd在恢复阶段通常被冻结为ssthresh,不用于直接限制发送,而是作为pipe的上界)时,才能发送新数据包。
步骤6:结合示例说明流程
接步骤1:cwnd_new = ssthresh = 10, RecoverFS = 20。
- 时刻T0(进入恢复):
prr_delivered=0,prr_out=0,pipe≈20,snd.nxt指向丢失的包(序列号X)。recover设为当前snd.nxt。 - 时刻T1(收到一个Dup-ACK,确认了序列号X-1的包):
prr_delivered增加1个MSS。- 模式:
snd.nxt(X) <recover-> PRR=SS。 limit = 1 * 10 / 20 - 0 = 0.5。由于不能发送半个包,通常实现会累积信用。假设信用足够发1个包。- 此时,发送端重传丢失的包X(重传优先于发送新数据)。
prr_out增加1,pipe不变(因为重传替代了发送)。
- 时刻T2(收到新ACK,确认了刚重传的包X以及后续一些包,假设总共确认了5个MSS):
prr_delivered增加5。- 模式:可能仍在PRR=SS。
limit = (0+1+5) * 10 / 20 - 1 = 6*0.5 -1 = 3 -1 = 2。现在有2个包的发送信用。pipe因为确认了5个包而减少5,假设现在pipe=15。cwnd是10,但pipe>cwnd是允许的,这是恢复阶段的特点。只要pipe < cwnd + 信用之类的条件(具体看实现),且有信用,就可以发送。- 发送端可以发送2个新数据包(序列号在
recover之后)。prr_out再增加2,pipe增加2变为17。
- 持续过程:随着ACK陆续到达,
prr_delivered增长,limit被持续计算。PRR=SS模式保证了总发送量prr_out大约是总交付量prr_delivered的一半。最终,当所有旧数据(序列号<recover)都被确认后,prr_delivered将等于RecoverFS(20)。此时,prr_out将被限制在20 * 10/20 = 10左右。同时,pipe也将逐渐下降到接近新的ssthresh(10)。 - 恢复结束:当收到大于等于
recover的ACK(即所有旧数据确认完毕),TCP退出快速恢复阶段,将cwnd设置为min(ssthresh, FlightSize + 1)等,进入拥塞避免阶段。此时发送速率已经平稳地过渡到了新的、减半后的窗口水平。
三、 总结与意义
- PRR的目标:实现快速恢复阶段发送速率的平滑、按比例削减,避免突发流量。
- 核心机制:通过追踪进入恢复后的交付量(
prr_delivered)和发送量(prr_out),并强制它们保持(ssthresh / 原始FlightSize)的比例(在PRR=SS模式下),来精确控制发送节奏。 - 效果:相比传统方法,PRR能减少恢复期间的包突发(burst),降低因突发导致的额外丢包或队列堆积风险,从而提升网络稳定性和整体吞吐量。它是对TCP快速恢复阶段发送引擎的一个重要优化。