Linux tc qdisc fq / fq_codel / cake 的公平队列算法在多租户环境中的适用性

7次阅读

fq 和 fq_codel 在多租户带宽隔离中经常失效,因其设计目标是抗 bufferbloat 和提升单主机多连接公平性,而非租户级硬限;它们统一哈希所有流、无 per-tenant 速率控制、无法绑定 cgroup 或 Namespace,且 drop 策略对短连接租户不公。

Linux tc qdisc fq / fq_codel / cake 的公平队列算法在多租户环境中的适用性

fq 和 fq_codel 在多租户带宽隔离中为什么经常“失效”

它们不是为严格租户隔离设计的——fqfq_codel 的核心目标是抗 bufferbloat 和提升单主机上多连接的公平性,而非跨用户/租户的带宽硬限。当多个租户共享同一台宿主机、且流量都经由同一个 qdisc 实例出向时,它们会把所有流(不管属于哪个 uid、cgroup 或 namespace)统一哈希进几百个队列,再按流粒度轮询调度。这意味着:

  • 一个租户发起 100 个 TCP 流,另一个只发 2 个,前者天然拿到约 50 倍的调度机会
  • 没有 per-tenant 的 rate limit 或 weight 配置入口,无法绑定 cgroup v2 或 net_cls classid
  • 如果租户间存在长连接 + 短突发混合场景,fq_codel 的 drop 策略可能优先惩罚短连接租户(因其 queue 建立晚、CE 标记滞后)

cake 能否直接用于租户级限速

不能直接替代 tc htb + Filter 组合,但比 fq/fq_codel 更接近可用:它内置了 natdiffserv4 模式,能基于五元组 + DSCP 分类,并支持 per-flow 速率平滑。不过关键限制在于:

  • cakebandwidth 参数是全局总带宽上限,不支持 per-class 或 per-filter 的子速率配置
  • 它识别 NAT 后的客户端 IP(需开启 nat),但无法感知容器 network namespace 或 pod UID —— 仍需配合 tc filter + flowid 把不同租户流量打上不同 classid
  • 在高并发小包场景(如微服务 mesh),cake 的内部分类开销比 htb 高约 15%~20%,实测 p99 延迟抖动更明显

真正可行的多租户带宽控制组合方案

必须分层:用 htb 做租户级硬限,用 fq_codelcake 做租户内流控。典型链路是:root qdisc = htb → 每个租户对应一个 htb class → 在每个 class 下挂 fq_codel(或 cake)作为 leaf qdisc。

  • 租户标识靠 tc filter + match ip src / ip dstcgroup(需内核 ≥ 5.10 + CONFIG_NET_CLS_CGROUP=y)
  • 避免在 root 上直接挂 fq_codel:它不响应 htb 的 borrow/ceil 机制,会导致租户超发后无法被有效压制
  • 若用 cake 作 leaf,务必关闭其 nat 模式(设 nat off),否则与上层 filter 的匹配逻辑冲突

容易被忽略的内核和命名空间兼容问题

很多线上环境跑着旧内核或容器运行时,导致看似配置正确却无效果:

  • fq_codelquantum 默认值(300)在 10G+ 网卡上易引发小包吞吐瓶颈,建议设为 1500;而 cakertt 默认 100ms,在容器内网(通常 rtt 1ms,否则 delay-based drop 完全不触发
  • 使用 cgroup v2 限速时,tc filter 必须搭配 match u32 + classid,且 cgroup path 必须挂载到 /sys/fs/cgroup/net_cls(非 unified)—— systemd 默认不启用该子系统
  • Pod 级限速(如 kubernetes)必须通过 CNI 插件注入 tc 规则,不能只在宿主机 root qdisc 上配置:容器网络独立于 host netns,host 上的 qdisc 对 pod 出向流量无效

真正卡住多租户限速的,从来不是算法选型,而是流量如何精准归类到租户维度。没做 tc filter 分流前,任何 fancy qdisc 都只是在对混合流“公平地乱发”。

text=ZqhQzanResources