受控延迟 (CoDel) 队列规则
AQM 代表主动队列管理;这些是处理路由器队列的算法。 RED 及其变体是主动队列管理算法。已经提出了一些用于在路由器处主动处理队列的其他算法。受控延迟就是这样的 AQM。 CoDel 是 Van Jacobson 和 Google 团队在 RFC 8289 中提出的。这是 AQM 的最新算法。
CoDel 是第一个使用“队列延迟”作为拥塞度量的 AQM 算法。以前的算法(RED 和变体)不使用“队列延迟”作为拥塞度量。 CoDel 的主要思想是尽量减少 'Bufferbloat' 的影响。 Bufferbloat 是当大量数据包到达并且其队列被填满并因此数据包开始丢弃时路由器的拥塞状态。与其他 AQM 算法相比,它的旋钮更少。 RED 有 4 个旋钮,但 CoDel 只有 2 个旋钮。旋钮基本上是用户必须配置的参数。 CoDel 算法在 RFC 8289 中描述。 CoDel 在 Linux 内核中实现。
CoDel 也存在一些变体。 Flow Queue CoDel (FQ-CoDel) 队列规则是在 Linux 内核中实现的流行的 CoDel 变体。 FQ-CoDel 在许多操作系统中用作默认队列规则。
CoDel – 蓝色备用 (COBALT) 队列规则。它是原始 CoDel 的另一个变体。它是对 CoDel 的改进,作为 Linux 中 CAKE 队列磁盘的一部分实现。
CoDel的工作:
CoDel 在“出队”期间运行。注意:CoDel 在“出队”期间在“输出端口”上运行!而 RED 在排队期间在输出端口运行。 CoDel 在输出端口队列中“在每个数据包离开时”进行操作。因此,没有调用 CoDel 的周期性时间间隔。它不像 CoDel 会每 100 毫秒或其他一些常规时间间隔调用一次。它仅在数据包从输出端口传出时调用。如果数据包没有到达(因此,不要离开),则不调用 CoDel 算法。
CoDel 算法分两个阶段运行。这两个阶段是 Dropping 和 Non-dropping 阶段。 CoDel 决定传出数据包是否应该出队或丢弃。 Dequeue 意味着 CoDel 成功地将数据包传输到下一个设备。丢弃意味着数据包没有被转发,而是被丢弃,发送者必须重新传输它。那么,CoDel 如何以及为何丢弃数据包。基本上,当 CoDel 检测到由于数据包出列而导致网络拥塞时,数据包就会被丢弃。
=> Two main components of CoDel:
=> Calculation of ‘instantaneous queue delay’.
=> Decision making logic (whether to dequeue or drop the outgoing packet)
CoDel 算法中的两个可配置参数:
RED 有 4 个可配置参数,而 CoDel 只有 2 个这样的参数。一是目标队列延迟,二是“时间间隔”。
- 目标(默认值:5 毫秒) [注意:这与自适应 RED 中的“目标队列延迟”相同]。 target 是允许的最大队列延迟。目标是 5 毫秒,这意味着入队的数据包不应在队列中停留超过 5 毫秒,它必须在 5 毫秒内出队。
- 间隔(默认:100ms) [注:假设互联网平均 RTT 为 100ms]。间隔是 CoDel 控制器等待拥塞自行恢复的时间量,如果没有发生,则控制器将处理拥塞状态。
算法:
第 1 步:在数据包入队时附加一个“时间戳”(以指示 enqueue_time)。它就像任何官方建筑的入口登记册。
第 2 步:计算每个传出数据包的当前队列延迟 (cur_qdelay):cur_qdelay = dequeue_time – enqueue_time (1)
第 3 步:当 cur_qdelay > target 第一次时,启动 'interval' 计时器。当当前队列延迟大于目标队列延迟时,表示路由器队列拥塞到足以使队列延迟超过目标值。
第 4 步:如果后续数据包的当前队列延迟始终大于间隔时间(100 毫秒)的目标(5 毫秒)值,则进入丢弃阶段,前提是 cur_qdelay > 整个“间隔”的目标。
- 步骤 (4A):如果 cur_qdelay > target 的时间小于 100 毫秒,则停留在非下降阶段,因为它是一个短脉冲。如果当前队列延迟大于更短的时间(即 20 毫秒、40 毫秒、99 毫秒),则不会过多关注拥塞。
- 步骤 (4B):如果 cur_qdelay > 目标持续 100 毫秒,则进入下降阶段。丢弃所有数据包直到 cur_qdelay > 目标,但丢弃数据包的时间由控制器计算。 next_drop_time = current_time + 间隔/sqrt(count) (2) 其中 count = 在此丢弃阶段丢弃的数据包数。
步骤5:当当前队列延迟再次小于目标时,退出丢弃阶段。'interval'定时器复位,等待cur_qdelay > target时再次设置。
下降阶段会发生什么:
在进入丢弃阶段,算法将丢弃传出的数据包。一旦算法进入丢弃阶段,数据包将立即被丢弃,而不是出队。现在,下一个数据包什么时候被丢弃?与 DropHead 或 DropTail 被动队列管理算法不同,CODEL 在丢弃阶段不会继续丢弃后续的出队数据包。相反,它会计算丢弃下一个数据包的确切时间。为了计算下一次丢包的准确时间,CoDel 使用了一个叫做 count 的局部变量,count 是丢包阶段到目前为止丢包的数量。因此,当第一个数据包被丢弃时,增加“计数”(表示在“此”丢弃阶段丢弃的数据包数量)。
使用控制法则计算丢弃下一个数据包的时间:
next_drop_time = current_time + interval/sqrt(count)
在丢弃阶段,CoDel 继续使用方程式丢弃数据包。 (2) 直到 cur_qdelay > 目标。所以,基本上我们想知道下降阶段什么时候结束?数据包何时停止丢弃? CoDel 将继续丢弃数据包,直到当前队列延迟大于目标延迟值。当当前队列延迟低于目标值时,CoDel 会感知到拥塞结束。因此,当 cur_qdelay < 目标时,退出下降阶段。当下降阶段停止时,局部变量被重置为其默认值。将计数值重置为 0。计数仅针对特定的丢弃阶段递增,在丢弃阶段结束时将被重置。进入不掉线阶段。
示例:假设数据包不断地进出队列。
目标:5 毫秒,间隔:100 毫秒,队列:enqueue->[11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1]->dequeue
=> 第一个数据包的当前队列延迟= 4ms,将此数据包出列。
=> 第 2 个数据包当前队列延迟 = 6ms,启动 100ms 的计时器并等待 100ms。计时器=0。这个数据包不会被丢弃,它会被转发到下一个设备。
=> 在 timer=8 时,第 3 个数据包的当前队列延迟 = 10ms,100ms 的计时器仍在运行,这个数据包不会被丢弃,它会被转发到下一个设备。
=> 在 timer=42 时,第 4 个数据包的当前队列延迟 = 44ms,仍然,100ms 的计时器正在运行,这个数据包不会被丢弃,它会被转发到下一个设备。
=> 在 timer=82 时,第 5 个数据包的当前队列延迟 = 41ms,仍然,100ms 的计时器正在运行,这个数据包不会被丢弃,它会被转发到下一个设备。
=> 在 timer=100 时,第 6 个数据包的当前队列延迟 = 25ms,但计时器刚刚到期,因此 codel 进入丢弃阶段。该数据包将立即被丢弃。
CoDel的工作在下降阶段:
=> 假设:current_time = 1000ms 并且 count = 1,那么 next_drop_time = current_time + interval/sqrt(count) (2)
- next_drop_time = 1000ms + 100ms/sqrt(1)
- next_drop_time = 1100 毫秒
=> 在时间=1100ms 时,丢弃下一个数据包,增加计数,计数=2。直到时间 = 1100 毫秒,不要丢弃任何数据包,只需将它们出列即可。
=> 在时间=1050,第 7 个数据包准备出队,它的当前队列延迟 = 60 毫秒,(60 毫秒 > 5 毫秒)所以 codel 仍处于丢弃阶段。它不会丢弃这个数据包,它会成功转发它。
=> 在时间=1090 时,第 8 个数据包已准备好出列,它的当前队列延迟 = 52 毫秒,(52 毫秒 > 5 毫秒)所以 codel 仍处于丢弃阶段。它不会丢弃这个数据包,它会成功转发它。
=> 假设第 9 个数据包直到 1100ms 才准备好出队,所以在 time=1100ms 时,第 9 个数据包将被丢弃。 current_time=1100 毫秒,计数=2,然后
- next_drop_time = 1100ms + 100ms/sqrt(2)
- next_drop_time = 1100ms + 71ms = 1171ms
=> 在时间=1171ms 时,丢弃下一个数据包,增加计数,计数=3。
=> 在时间=1160 时,第 10 个数据包已准备好出列,它的当前队列延迟 = 70 毫秒,(70 毫秒 > 5 毫秒),因此编解码器仍处于丢弃阶段。它不会丢弃这个数据包,它会成功转发它。
=> 假设第 11 个数据包直到 1171ms 才准备好出列,所以在 time=1171ms 时,第 11 个数据包将被丢弃。
- current_time=1171 毫秒,计数=3,然后
- next_drop_time = 1100ms + 100ms/sqrt(3)
- next_drop_time = 1100ms + 58ms = 1229ms
=> 在时间=1229ms 时,丢弃下一个数据包,增加计数,计数=4。
CoDel 的已知问题:
当某些流无响应时,队列控制会丢失。响应流是 UDP 流。为了管理此类流的队列,正在提出另一种算法。它被称为 FQ-CoDel。它代表流队列代码。 FQ-CoDel 通过为每个流提供单独的队列来隔离流。我们知道这些 AQM 算法部署在路由器上。路由器为每个流维护一个单独的队列。路由器非常聪明,它很容易唯一地识别每个流。流通过以下方式标识:源 IP、源端口、目标 IP、目标端口和协议号。