bbr-congestion-control-00 State Machine

Initialization Steps

 Upon transport connection initialization, BBR executes the following  steps:

 
BBRInit():
       init_windowed_max_filter(filter=BBR.BtlBwFilter, value=0, time=0)
       BBR.rtprop = SRTT ? SRTT : Inf //BBR.rtprop(往返传播延迟)设置为平滑的往返时间(SRTT),如果可用的话。如果 SRTT 不可用,则将 BBR.rtprop 设置为无穷大(Inf)。往返传播延迟是 BBR 用于控制发送速率的重要参数。
       BBR.rtprop_stamp = Now() //将 BBR.rtprop 的时间戳设置为当前时间。它用于跟踪上次更新 BBR.rtprop 的时间
       BBR.probe_rtt_done_stamp = 0 //在探测 RTT 阶段用于跟踪一轮探测的完成情况
       BBR.probe_rtt_round_done = false //在探测 RTT 阶段用于跟踪一轮探测的完成情况
       BBR.packet_conservation = false //将 packet_conservation 标志设置为 false。它用于确定在丢包恢复期间是否要保护数据包
       BBR.prior_cwnd = 0//将 prior_cwnd 变量初始化为 0。在进入丢包恢复之前,它用于存储先前的拥塞窗口大小
       BBR.idle_restart = false
       BBRInitRoundCounting()
       BBRInitFullPipe()//设置必要的变量和标志以启用满管道操作
       BBRInitPacingRate()//初始化 BBR 的 pacing rate。它设置初始 pacing rate 和相关的变量。
       BBREnterStartup()//将 BBR 切换到启动阶段,在该阶段 BBR 进行探测并学习网络路径的特性。

   packet_conservation是一个标志(flag),用于控制丢包恢复期间是否保护数据包。当packet_conservation被设置为true时,BBR会尽量保护已发送但未确认的数据包,以减少进一步的丢包。这种保护机制在网络出现拥塞或丢包时起到了重要作用,可以帮助BBR更好地适应网络变化,并减少数据包的丢失。当packet_conservation被设置为false时,BBR则不进行数据包保护,可能会导致更多的数据包丢失。packet_conservation的设置在BBR的状态转换和丢包恢复过程中起到重要的控制作用,以确保在网络条件变化时能够采取适当的措施保护数据传输的稳定性。

Startup


upon entry into Startup state BBR sets BBR.pacing_gain and BBR.cwnd_gain to BBRHighGain = 2/ln(2) ~= 2.89, the minimum gain value that will
allow the sending rate to double each round. when initializing a connection, or upon any later entry into Startup mode, 
BBR executes the following BBREnterStartup() steps:
     BBREnterStartup():
       BBR.state = Startup
       BBR.pacing_gain = BBRHighGain
       BBR.cwnd_gain = BBRHighGain

  BBR.filled_pipe是一个布尔值,用于记录BBR是否估计它曾经充分利用了可用带宽(即"填满了管道")。如果BBR注意到有连续几轮尝试将交付速率翻倍,但实际上增长很小(少于25%),那么它会估计已经达到了BBR.BtlBw,将BBR.filled_pipe设置为true,退出启动阶段并进入排空(Drain)阶段。

  换句话说,在启动阶段,BBR试图不断增加发送速率,以探测网络的可用带宽。如果连续三轮的发送速率加倍的尝试都没有显著增加交付速率(增长少于25%),则BBR认为已经接近网络的瓶颈带宽(BBR.BtlBw),并认为管道已经充分填充。此时,BBR将退出启动阶段,并进入排空阶段,开始使用更保守的发送策略来维持网络的稳定性

BBRInitFullPipe():
       BBR.filled_pipe = false
       BBR.full_bw = 0
       BBR.full_bw_count = 0
 Once per round trip, upon an ACK that acknowledges new data, and when the delivery rate sample is not application-limited (see
   [draft-cheng-iccrg-delivery-rate-estimation]), BBR runs the "full pipe" estimator, if needed:
BBRCheckFullPipe():
       if BBR.filled_pipe or
          not BBR.round_start or rs.is_app_limited
         return  // no need to check for a full pipe now
       if (BBR.BtlBw >= BBR.full_bw * 1.25)  // BBR.BtlBw still growing?
         BBR.full_bw = BBR.BtlBw    // record new baseline level
         BBR.full_bw_count = 0
         return
       BBR.full_bw_count++ // another round w/o much growth
       if (BBR.full_bw_count >= 3)
         BBR.filled_pipe = true

  BBR等待三个轮次是为了确保发送方没有误判接收速率出现了暂时性的平台期。允许三个轮次的时间是为了让接收方的接收窗口自动调整打开接收窗口,并让BBR发送方意识到BBR.BtlBw应该更高:在第一轮中,接收窗口自动调整算法增加了接收窗口;在第二轮中,发送方填充了较大的接收窗口;在第三轮中,发送方获得了更高的交付速率样本。这个三轮的阈值是通过YouTube的实验数据进行验证的。

  换句话说,BBR等待三个轮次是为了避免误判接收速率出现了暂时性的平台期。这样做是为了给接收方足够的时间自动调整接收窗口,并让发送方获得更准确的BBR.BtlBw估计。这个三轮的等待时间是通过实际的实验数据进行验证的,以确保在不同网络环境下的稳定性和可靠性。

Drain

       In Startup, when the BBR "full pipe" estimator estimates that BBR has filled the pipe, BBR switches to its Drain state.

In Drain, BBR aims to quickly drain any queue created in Startup by switching to a pacing_gain well below 1.0;

specifically, it uses a pacing_gain that is the inverse of the value used during Startup, which drains the queue in one round: 

BBREnterDrain():
       BBR.state = Drain
       BBR.pacing_gain = 1/BBRHighGain  // pace slowly
       BBR.cwnd_gain = bbr_high_gain    // maintain cwnd

 

In Drain, when the number of packets in flight matches the estimated BDP, meaning BBR estimates 
that the queue has been fully drained but the pipe is still full, then BBR leaves Drain and enters ProbeBW. To implement this, upon every ACK BBR executes:
 BBRCheckDrain():
       if (BBR.state == Startup and BBR.filled_pipe)
         BBREnterDrain()
       if (BBR.state == Drain and packets_in_flight <= BBRInflight(1.0))
         BBREnterProbeBW()  // we estimate queue is drained

 

ProbeBW

 在BBR中,流程大部分时间都处于ProbeBW状态,通过一种称为增益循环(gain cycling)的方法进行带宽探测,以实现高吞吐量、低排队延迟和公平的带宽分配。增益循环中,BBR会按照一系列的pacing_gain数值进行循环。具体而言,它采用了一个包含八个阶段的循环,对应的pacing_gain数值依次为:5/4、3/4、1、1、1、1、1、1。每个阶段通常持续大约BBR.RTprop的时间,其中BBR.RTprop表示往返传播延迟。

   在增益循环的第0阶段,BBR使用一个pacing_gain为5/4,通过逐渐增加inflight(发送中的数据量)来探测更多的带宽。它会持续执行直到经过的时间达到至少BBR.RTprop,并且inflight达到了5/4乘以estimated_BDP(如果BBR.RTprop较低,这可能需要更长的时间),或者发生了一些数据包的丢失(这可能表明路径无法容纳5/4乘以estimated_BDP的inflight)

 BBR's ProbeBW gain cycling algorithm operates as follows.

Upon entering ProbeBW, BBR (re)starts gain cycling with the following:

 BBREnterProbeBW():
       BBR.state = ProbeBW
       BBR.pacing_gain = 1
       BBR.cwnd_gain = 2
       BBR.cycle_index = BBRGainCycleLen - 1 - random_int_in_range(0..6)
       BBRAdvanceCyclePhase()
BBRCheckCyclePhase():
       if (BBR.sate == ProbeBW and BBRIsNextCyclePhase()
         BBRAdvanceCyclePhase()

     BBRAdvanceCyclePhase():
       BBR.cycle_stamp = Now()
       BBR.cycle_index = (BBR.cycle_index + 1) % BBRGainCycleLen
       pacing_gain_cycle = [5/4, 3/4, 1, 1, 1, 1, 1, 1]
       BBR.pacing_gain = pacing_gain_cycle[BBR.cycle_index]

     BBRIsNextCyclePhase():
       is_full_length = (Now() - BBR.cycle_stamp) > BBR.RTprop
       if (BBR.pacing_gain == 1)
         return is_full_length
       if (BBR.pacing_gain > 1)
         return is_full_length and
                   (packets_lost > 0 or
                    prior_inflight >= BBRInflight(BBR.pacing_gain))
       else  //  (BBR.pacing_gain < 1)
         return is_full_length or
                    prior_inflight <= BBRInflight(1)

"prior_inflight" is the amount of data that was in flight before processing this ACK.

BBRAdvanceCyclePhase()函数的作用是推进增益循环的阶段。具体流程如下:

  1. 获取当前时间,并将其赋值给BBR.cycle_stamp,用于记录当前阶段的开始时间。
  2. 更新BBR.cycle_index,通过将其增加1并取模BBRGainCycleLen,使其在[0, BBRGainCycleLen-1]之间循环。
  3. 定义一个pacing_gain_cycle数组,其中包含了增益循环的pacing_gain数值序列。
  4. 根据BBR.cycle_index从pacing_gain_cycle数组中获取相应的pacing_gain数值,并将其赋值给BBR.pacing_gain。

BBRIsNextCyclePhase()函数用于判断是否进入增益循环的下一个阶段。具体流程如下:

  1. 判断是否到达了完整的BBR.RTprop时间段,通过判断(Now() - BBR.cycle_stamp)是否大于BBR.RTprop。
  2. 如果当前的pacing_gain值为1,返回是否到达了完整的BBR.RTprop时间段的结果。
  3. 如果当前的pacing_gain值大于1,返回是否同时满足以下条件的结果:
    • 已经发生了数据包丢失(packets_lost > 0),或者
    • 前一阶段的inflight数据量(prior_inflight)大于等于当前pacing_gain对应的BBRInflight(BBR.pacing_gain)值。
  4. 如果当前的pacing_gain值小于1,返回是否满足以下条件的结果:
    • 已经达到了完整的BBR.RTprop时间段,或者
    • 前一阶段的inflight数据量(prior_inflight)小于等于pacing_gain为1时对应的BBRInflight(1)值。

Restarting From Idle

   当BBR从空闲状态重新启动时,它保持拥塞窗口(cwnd)不变,并以确切的BBR.BtlBw(瓶颈带宽)对数据包进行调度,以便尽快返回到速率平衡和完整管道的目标操作点。具体而言,如果流的BBR.state为ProbeBW,并且流受应用程序限制,并且当前没有正在传输的数据包,那么在流发送一个或多个数据包的时刻,BBR将设置BBR.pacing_rate为确切的BBR.BtlBw

  1. 如果没有正在传输的数据包(packets_in_flight == 0)并且流受应用程序限制(C.app_limited),将BBR.idle_start设置为true。
  2. 如果BBR的状态为ProbeBW,则调用BBRSetPacingRateWithGain(1)函数将调度速率设置为确切的BBR.BtlBw。

 

ProbeRTT

       To help probe for BBR.RTprop, BBR flows cooperate to periodically drain the bottleneck queue using a state called ProbeRTT, when needed. In any state other than ProbeRTT itself, if the RTProp estimate has not been updated (i.e., by getting a lower RTT measurement) for more than ProbeRTTInterval = 10 seconds, then BBR enters ProbeRTT and reduces the cwnd to a minimal value, BBRMinPipeCwnd (four packets). After maintaining BBRMinPipeCwnd or fewer packets in flight for at least ProbeRTTDuration (200 ms) and one round trip, BBR leaves ProbeRTT and transitions to either Startup or ProbeBW, depending on whether it estimates the pipe was filled already.

  为了探测BBR.RTprop(往返时延估计),BBR流通过一种称为ProbeRTT的状态定期协作来周期性地排空瓶颈队列。在除ProbeRTT本身以外的任何状态中,如果RTProp估计已经超过ProbeRTTInterval = 10秒没有更新(即没有获取更低的RTT测量值),那么BBR将进入ProbeRTT状态,并将cwnd降低到最小值BBRMinPipeCwnd(四个数据包)。在维持BBRMinPipeCwnd或更少数量的数据包在传输中至少持续ProbeRTTDuration(200毫秒)和一个往返时延的时间后,BBR将离开ProbeRTT状态,并根据是否估计管道已经被填满,过渡到Startup或ProbeBW状态之一。

  ProbeRTT持续的时间足够长(至少ProbeRTTDuration = 200毫秒),以使具有不同RTT的流具有重叠的ProbeRTT状态,同时又足够短,将ProbeRTT的cwnd限制带来的吞吐量惩罚控制在大约2%(200毫秒/10秒)的范围内

  BBR的BBR.RTprop过滤窗口、RTpropFilterLen和ProbeRTT状态之间的时间间隔ProbeRTTInterval是相互配合的。BBR的实现必须使用RTpropFilterLen等于或长于ProbeRTTInterval,并且为了与其他BBR流进行协调,必须使用标准的ProbeRTTInterval,即10秒。建议同时使用10秒作为RTpropFilterLen和ProbeRTTInterval。10秒的RTpropFilterLen足够短,以便在流量水平或路由发生变化时快速收敛,但又足够长,使得交互应用程序(例如网页、远程过程调用、视频片段)通常在窗口内具有自然的静默期或低速期,在此期间流的速率足够低或足够长,可以排空其在瓶颈处的队列。然后,BBR.RTprop过滤器可以机会性地获取这些BBR.RTprop测量值,并且在不需要ProbeRTT的情况下进行RTProp刷新。这样,流通常只需要支付2%的吞吐量惩罚,如果有多个大量流同时在整个ProbeRTTInterval窗口发送数据。

 

RTpropFilterLen(RTprop过滤器长度)是BBR算法中的一个参数,用于控制RTprop(往返时延估计)的过滤和更新行为。RTpropFilterLen的作用如下:

  1. 过滤RTprop测量:BBR使用RTpropFilterLen来确定在一段时间内需要考虑多少个RTprop测量值。只有在过滤器窗口中的测量值才会被用于计算平均RTprop,并用于算法的决策过程。通过设置适当的RTpropFilterLen,可以排除过早或过晚的测量值对BBR算法的影响,提高RTprop估计的准确性。

  2. 平滑RTprop变化:由于网络条件的变化,RTprop可能会出现短期的波动或抖动。使用RTpropFilterLen可以平滑这些变化,使得RTprop估计更加稳定和可靠。较长的RTpropFilterLen可以在一定程度上减小RTprop的波动,使得BBR算法更好地适应网络状况的变化。

  3. 协调多个BBR流:BBR算法通过ProbeRTT状态协作地探测和排空瓶颈队列。在多个BBR流共存的情况下,它们需要协调ProbeRTT状态的触发和持续时间,以避免过多的竞争和拥塞。通过设置RTpropFilterLen等于或长于ProbeRTTInterval,不同的BBR流可以在相同的ProbeRTT时间间隔内进行状态切换,从而实现更好的协作和公平共享网络带宽。

总之,RTpropFilterLen的作用是控制RTprop测量的过滤和平滑,以及协调多个BBR流的ProbeRTT状态,从而提高BBR算法的性能和公平性。

On every ACK BBR executes BBRCheckProbeRTT() to handle the steps related to the ProbeRTT state as follows:
 BBRCheckProbeRTT():
       if (BBR.state != ProbeRTT and
           BBR.rtprop_expired and
           not BBR.idle_restart)
         BBREnterProbeRTT()
         BBRSaveCwnd()
         BBR.probe_rtt_done_stamp = 0
       if (BBR.state == ProbeRTT)
         BBRHandleProbeRTT()
       BBR.idle_restart = false

     BBREnterProbeRTT():
       BBR.state = ProbeRTT
       BBR.pacing_gain = 1
       BBR.cwnd_gain = 1

     BBRHandleProbeRTT():
       /* Ignore low rate samples during ProbeRTT: */
       C.app_limited =
         (BW.delivered + packets_in_flight) ? : 1
       if (BBR.probe_rtt_done_stamp == 0 and
           packets_in_flight <= BBRMinPipeCwnd)
         BBR.probe_rtt_done_stamp =
           Now() + ProbeRTTDuration
         BBR.probe_rtt_round_done = false
         BBR.next_round_delivered = BBR.delivered
       else if (BBR.probe_rtt_done_stamp != 0)
         if (BBR.round_start)
           BBR.probe_rtt_round_done = true
         if (BBR.probe_rtt_round_done and
             Now() > BBR.probe_rtt_done_stamp)
           BBR.rtprop_stamp = Now()
           BBRRestoreCwnd()
           BBRExitProbeRTT()

     BBRExitProbeRTT():
       if (BBR.filled_pipe)
         BBREnterProbeBW()
       else
         BBREnterStartup()

 

 

posted @ 2023-06-26 17:26  codestacklinuxer  阅读(30)  评论(0编辑  收藏  举报