主动队列管理:RED → CoDel → FQ-CoDel

💡 原文中文,约31700字,阅读约需76分钟。
📝

内容提要

本文梳理主动队列管理(AQM)的原理与演进。尾丢弃队列仅在缓冲区满时丢包,缓冲区越大延迟越高,即 bufferbloat。RED 依据平均队列长度提前随机丢包,但阈值按包数计,延迟随链路速率变化,调参困难。CoDel 改以逗留时间为信号,无需获知链路速率。FQ-CoDel 先按流隔离,再控制每流延迟,保护稀疏流。实验对比四种队列表明,AQM 显著降低延迟,但流数较多时排队时间仍高于目标。

🔎

延伸解读

从队列长度到排队时间:AQM 设计信号的转变

RED 以平均队列长度作为拥塞信号,但队列长度需换算为延迟,必须知道链路速率;同一组包数阈值在 5 Mbit/s 和 100 Mbit/s 上对应的排队时间相差 20 倍,导致 RED 在低速链路上队列过长、高速链路上利用率下降。CoDel 改用逗留时间(排队时间)作为信号,无需获知链路速率,在速率变化的链路上也成立。RFC 7567 将 AQM 的定义从“控制队列长度”扩展为“控制队列长度和/或包在队列中停留的平均时间”,正是这一转变的规范化表述。

FQ-CoDel 的流隔离:保护稀疏流,但代价不容忽视

FQ-CoDel 通过按流哈希到多个队列并优先服务新流,使稀疏流(如 VoIP、DNS)的延迟大幅降低。实验中探测流在 FQ-CoDel 下的 p99 延迟仅 1.35 ms,而 RED 和 CoDel 下约为 36-41 ms。但流隔离也带来代价:FQ-CoDel 的利用率最低(90.2%),短流完成时间的 p95 反而高于 CoDel(465 ms vs 298 ms),原因是短流在慢启动末期可能因整个 qdisc 的积压而触发 CoDel 丢包,导致超时重传。

流数增多时,AQM 无法将延迟控制在目标值

实验显示,当 20 Mbit/s 链路上的长流从 1 条增加到 32 条时,CoDel 和 FQ-CoDel 的排队时间中位数从 0.7 ms 升至约 18.5 ms,远高于 5 ms 的 target。这是因为每条流的窗口不能低于 1 到 2 个包,维持如此小的窗口需要极高的丢包率,而 AQM 只能决定多余的窗口以队列还是丢包形式存在。这是所有基于丢包或标记的 AQM 共同的边界,流数与 BDP 之比很大时如何保持低延迟仍是开放问题。

部署 AQM 时需注意的实践陷阱

文章指出,net.core.default_qdisc 只影响新创建的 qdisc,已激活的设备不会自动替换。实测中 sysctl 显示 fq_codel,但网卡上实际运行的是 pfifo_fast。此外,sch_codel 默认关闭 ECN,而 sch_fq_codel 默认开启;RED 的阈值按包数计,换链路后需重新调整。AQM 只在队列形成的那一跳起作用,家用宽带瓶颈通常在调制解调器或运营商设备上,需在网关上整形到略低于瓶颈速率才能让队列可控。

❓

Q&A

什么是 bufferbloat?它为什么会导致高延迟?

bufferbloat 指网络路径上普遍存在的过大、常满的缓冲区。在尾丢弃队列中,丢包只在队列满时发生,TCP 拥塞控制会一直加大窗口直到丢包,导致稳态下队列总是接近满的。排队延迟等于积压字节除以链路速率,因此缓冲区越大,排队延迟越高。

RED 和 CoDel 在拥塞信号上有什么根本区别?

RED 使用平均队列长度作为拥塞信号,需要知道链路速率才能换算成延迟,阈值按包数计,延迟随链路速率变化,调参困难。CoDel 使用包的逗留时间(排队时间)作为信号,直接就是延迟,无需知道链路速率,在速率变化的链路上也成立。

FQ-CoDel 如何保护稀疏流(如 VoIP 或 DNS)?

FQ-CoDel 先按流哈希到多个队列,出队时优先服务新流(new_flows)。一条流只要在每次发包之间让自己的队列排空,它的下一个包就会以新流身份进入 new_flows,排在所有积压流前面,从而获得低延迟。实验显示探测流在 FQ-CoDel 下的时延中位数仅 0.32 ms,p99 为 1.35 ms。

为什么 RED 的阈值参数换一条链路就不合适?

RED 的阈值以包数或字节数计,同样的阈值在不同链路速率下对应的延迟不同。例如 60 个包的阈值在 5 Mbit/s 上是 144 ms,在 100 Mbit/s 上只有 7.2 ms。因此 RED 需要按链路速率分别配置阈值,否则低速链路上队列太长,高速链路上阈值相对 BDP 太小、队列经常排空,利用率下降。

CoDel 的控制律是如何工作的?

CoDel 在丢弃态中,第 n 次丢包后下一次丢包安排在 t_{n+1} = t_n + interval / sqrt(n)。这样丢包频率随时间线性上升,逐步压低总速率,直到逗留时间回到 target 以下。该控制律基于 TCP 吞吐与丢包概率的 sqrt(p) 关系。

Linux 默认的队列规则是 fq_codel 吗?

不是。Linux v6.12 内核的默认排队规则仍是 pfifo_fast。把默认值改成 fq_codel 的是 systemd 自带的 sysctl 配置(net.core.default_qdisc = fq_codel),但该设置只影响之后创建的默认 qdisc,已存在的 qdisc 不会被替换。实际使用的队列规则需用 tc qdisc show 查看。

AQM 在什么位置才能有效控制延迟?

AQM 只控制它所在那一跳的队列。如果瓶颈在调制解调器或运营商设备上,主机或路由器上的 AQM 面对的是更高速率的内部链路,队列不会在那里形成,规则不会触发。常见做法是在网关上把出口整形到略低于实际瓶颈速率,让队列在自己可控的地方形成,例如用 HTB 或 CAKE 整形。

L4S 与 FQ-CoDel 在架构上有什么不同?

FQ-CoDel 按流隔离,为每条流维护队列并控制每流延迟;L4S 只用两个队列,按 ECN 码点区分流量,让使用可扩展拥塞控制的流进入浅阈值的 L 队列,经典流进入 C 队列,并通过耦合保持公平。FQ-CoDel 需要读取传输层标识,而 L4S 只看 IP 头的 ECN 字段。

🏷️

标签

➡️

继续阅读