从第一性原理出发,eBPF EDT (Earliest Departure Time) 的核心思想是将流量控制从**“基于空间的排队(Buffer-based)”转变为“基于时间的调度(Time-based)”**。
在传统的 HTB 或 IFB 方案中,我们通过在内存中开辟一个“池子”(队列)来吸收突发流量;而在 EDT 模型中,内核不再关注包存放在哪,而是关注包何时准许发送。
结论
eBPF EDT 通过在数据包(skb)上打一个**“离网时间戳”**来取代物理队列。它利用 eBPF 灵活的计算能力和内核 FQ (Fair Queue) 调度器的时钟控制,在不增加中间设备(如 IFB)、不引入上下文切换开销的情况下,实现了极其精准的流量整形(Shaping)。
1. EDT 的底层运作机制
EDT 的核心逻辑由两部分协同完成:一个 eBPF 程序 和一个 FQ Qdisc。
A. 计算离网时间(BPF 逻辑)
每当一个数据包准备从网卡发出时,挂载在 tc egress 的 eBPF 程序会执行以下逻辑:
-
查询速率限制:从 BPF Map 中读取该容器/流对应的带宽限制(Rate)。
-
计算时间间隔:根据包的大小(Len),计算发送这个包理论上需要占用的时间。
-
确定发送时间:
- 读取该流上一个包的“预定发送时间”(LastDepartureTime)。
- 当前包的发送时间 = 。
-
打戳:将这个计算出的时间直接写入
skb->tstamp字段。 -
状态更新:将最新的发送时间更新回 BPF Map,供下一个包使用。
B. 强制执行(FQ Qdisc 逻辑)
当 skb 带着 tstamp 到达物理网卡的 FQ 队列时:
- 如果
tstamp是过去的时间,立即发送。 - 如果
tstamp是未来的时间,FQ调度器会将包挂起,直到系统时钟达到该时间戳。
2. 为什么它能比 IFB 减少重传?(第一性原理分析)
IFB 虽然也有缓冲区,但它本质上还是一个“黑盒”:包进去了,满了就丢。而 EDT 是**“节奏控制器”(Pacing)**。
-
平滑发送节奏(Anti-Burst) :
传统的限速(Policing)在令牌桶满时会瞬间放行一堆包,空了则全部丢弃。EDT 强制每一个包之间必须有精准的 间隔。对于 TCP 协议栈来说,它看到的不是丢包,而是稳定的、稍微变长的 RTT。这会诱导 TCP 的拥塞控制算法(如 BBR)自动进入一个平稳的发送状态,而不是反复进入“丢包-重传”的恶性循环。
-
零拷贝与无上下文切换:
IFB 需要将数据包从物理网卡
ingress路径重定向(Redirect)到虚拟网卡的egress路径,这涉及到 CPU 在不同网络路径间的跳转。EDT 直接在原始skb上修改时间戳,数据包在内存中原地不动,直接由物理网卡的调度器处理。
3. EDT vs. IFB vs. Policing 深度对比
| 特性 | Policing (Drop) | IFB + HTB | eBPF + EDT (Cilium) |
|---|---|---|---|
| 内核路径 | 原地丢弃 | 重定向到虚拟设备 | 原地打戳 |
| 限速手段 | 剪裁(Clip) | 排队(Buffer) | 节奏控制(Pace) |
| 重传风险 | 极高(信号剧烈) | 中(依赖队列长度) | 极低(信号平滑) |
| 多核扩展性 | 优秀 | 差(全局锁竞争) | 极优(Per-CPU 状态) |
| 资源消耗 | 极低 | 较高(内存+CPU切换) | 中(仅 BPF 计算开销) |
4. 事实与实践:Cilium 的做法
作为 Kube-OVN 的维护者,你可能关注到 Cilium 在 1.6 版本后全面转向了 EDT 模式。
- 实现细节:Cilium 在 BPF Map 中维护了
struct bandwidth状态,记录了每个 Cgroup 或 IP 的last_tstamp。 - 硬件卸载:由于 EDT 只是给包打上一个
tstamp,现代的高级网卡(如 Mellanox ConnectX 系列)甚至可以识别这个时间戳,直接在硬件层级进行 Pacing 发送,从而将 CPU 压力彻底卸载。
思考与推测
在 Kube-OVN 的背景下,目前基于 OVS 的流量控制更多依赖于 OVS 自带的 ingress_policing_rate(基于丢包)或 QoS 模块。如果 Kube-OVN 要实现类似的 EDT 效果,可能需要:
- 旁路掉 OVS 的默认限速,在
veth宿主机端挂载 eBPF 逻辑。 - 确保物理网卡上配置了
FQ作为根 Qdisc(这可能与现有的 OVS 队列策略冲突)。