跳转到主要内容

eBPF 瞬时 TCP 故障恢复基准必须覆盖哪些场景才有意义?

简短回答: 边界在于你重定向数据包所在的层,与连接状态真正所在的层之间的差距。eBPF 的 XDP 或 TC 程序把帧在网卡之间移动,或送入用户态;它既不能移动、复制,也不能重建套接字的传输控制块(TCB),更无法重建应用层正在进行的请求/响应状态。因此,只有当基准测试在特定层注入故障、在该层以及上一层都断言恢复、并且明确区分"数据包再次流动"这一朴素检查所混为一谈的三种结果——数据路径已重路由、内核 TCP 连接存活、应用操作完成——时,它才具有意义。

此类原型通常报告四个量:恢复延迟、故障切换期间的丢包、套接字/连接状态和 CPU 开销——它们各自绑定到不同层。关键设计决策是每个指标在哪个层被断言。如果每个指标都只在数据路径上采样,那么基准测试可能显示"通过",而它声称已恢复的连接其实早已被内核重置。

连接状态真正所在的层

TCP 连接状态不是数据包流的属性。RFC 9293 把连接记录定义为传输控制块(TCB),它保存本地与远端地址和端口、发送与接收序号(SND.UNA、SND.NXT、RCV.NXT)、窗口值,以及指向发送/接收缓冲区和重传队列的指针(参见 RFC 9293 §3.3.1 关键连接状态变量)。TCB 位于内核套接字中,而非线格式里。

这正是基准测试必须关心的边界。eBPF 数据路径 hook 操作的是 sk_buff 或 xdp_buff 数据,只能返回 redirect/pass/drop 动作。XDP 重定向机制查找目标(DEVMAP、DEVMAP_HASH、CPUMAP 或 XSKMAP),把帧排入每 CPU 批量队列,并在 NAPI 轮询循环内刷新。AF_XDP 文档明确点出了这种不对称性:UMEM 完成项"把帧的所有权交还用户态,但本身并不保证该数据包已成功发送"(AF_XDP 文档)。把帧移到新网卡或用户态环形队列,并不能说明拥有该流的 TCB 是否仍然存在。

因此,下面三个陈述并不等价,基准测试不能把它们当作同一件事来打分:

  1. 数据路径已恢复——数据包现在通过目标网卡或 XSK。这是 XDP/TC 重定向能够直接证明的唯一事实。
  2. 连接已恢复——该流的内核 TCP 套接字仍处于 ESTABLISHED(或某种恢复状态),且其序号/窗口空间完好。这是 TCB 的属性,而非线格式的属性。
  3. 应用已恢复——正在进行的请求已完成,或新请求成功,调用方未见到间隙、重复或重置。

数据路径重定向可能实现 (1),同时 (2) 和 (3) 为假:TCB 因无关原因被内核重置,或应用的未决请求被丢弃、其客户端重试产生了一个全新的、无关的操作。

故障场景矩阵

哪些场景"必须",取决于基准测试所要支撑的结论:当结论覆盖某个失效模式所在的层时,隔离该失效模式的场景才是必须的。下面每个场景都隔离了一种由不同层拥有的失效模式;省略其中之一,就意味着该层的缺陷可以藏在绿色的数据路径计数之后,而结论横跨多层的基准测试,也需要横跨相同层的场景。

故障位于数据路径(接口、队列或重定向目标)。 在流处于活跃状态时,关闭或重新编程转发路径。如果本地地址和套接字归属仍然有效,而且路由已经收敛,内核 TCP 协议栈应当保留 TCB,并在新路径上重传。基准测试必须断言:(a) 数据包在目标网卡/XSK 上恢复流动,且 (b) 套接字的序号计数器没有回退、没有为该流发出 RST。这是"恢复延迟"成为一个有定义数值的情形:它等于死路径上最后一个数据包到活路径上第一个被接受数据包之间的时间。

故障位于内核套接字(TCB 消失)。 强制套接字被重置——销毁拥有它的进程、让内核耗尽该流的重传预算、或触发 ABORT——然后重新建立连接。此时数据路径可以始终健康,但连接已不存在。基准测试必须表明,恢复服务的是新握手(或应用的重连),并测量重连延迟,而不是宣称重定向恢复了连接。内核的相关控制项记录在 IP sysctl 参考中,并且比单一截止值更微妙:给定值 N,tcp_retries2 描述的是一个在指数 RTO 退避下的假想连接——它会重传 N 次,并在第 (N+1) 个 RTO 处被杀死;默认值 15 对应 924.6 秒,文档明确把这称为有效超时的下界——Linux 会在第一个超过假想超时值的 RTO 处真正超时,因此真实截止值跟随该连接实际测得的 RTO(受 tcp_rto_min_us 和 tcp_rto_max_ms 约束),而不是某个普适数字。keepalive 是另一个可选的机制:RFC 9293 §3.8.4 TCP Keep-Alives 规定 keep-alive 是可选的、按连接开关、且默认关闭,而 Linux 只有在套接字上"启用了 keepalive"时才按照 tcp_keepalive_time、tcp_keepalive_probes、tcp_keepalive_intvl 进行计数。因此,一条 keepalive 关闭且没有未确认数据的空闲连接根本不会被 tcp_retries2 杀死——因为没有任何东西在重传。基准测试必须点名它所测试的机制:有未确认数据在途时,跨越有效的 tcp_retries2 边界;连接静默时,要么启用 keepalive 并跨越 keepalive 预算,要么明确说明该套接字在设计上就该在中断中存活。把两种机制混在一起,正是基准测试在"恢复"的同时底层存活规则各不相同的原因。

故障位于应用层(正在进行的操作丢失)。 这一层的不同丢失有不同的责任方。在存活的 TCB 上,已发送但尚未确认的段属于传输层丢失:标准的恢复是 TCP 自身的重传,而不是应用重试——字节留在发送方的重传队列中,并在套接字边界之下被修复(RFC 9293 §2.2 把可靠性定义为"通过重传进行纠正";§3.8.1 与 §3.8.3 的 R1/R2 失败阈值定义了这种修复何时放弃)。传输层无法修复的是线路之上的边界:一个在提交操作之后、响应到达调用方之前崩溃的应用,无法从 TCP 判断该操作是否完成;那里的重试是应用层自己的决定,其恰好一次的行为来自应用语义(幂等键、去重、事务标记)——而不是来自 TCP 或数据路径。因此,基准测试必须注入带序列标记的合成操作,并在应用可见的结果上断言——按应用声明的重试语义判定被标记操作是否完成——而不是看包计数,并且必须区分被重传吸收的丢失与对调用方可见的丢失。正是在这一层,"故障切换期间的丢包"可以非零却仍与一次正确的运行一致,因为丢失在应用边界之下被传输层吸收了。

故障位于重定向目标(过期或空的映射项)。 XSKMAP 与 DEVMAP 项是重定向的目标。AF_XDP 文档指出:重定向时 XDP 会校验映射该项处的 XSK 确实绑定在该设备与队列上,XSK 不匹配或映射槽为空时,数据包会被丢弃而非投递。基准测试必须演练空或过期的目标,并断言该丢弃被计数而非被静默吸收。这是纯延迟直方图会完全遗漏的失效模式,因为没有任何东西到达"已恢复"路径。

故障长于单次恢复(持续中断超过任何单次恢复)。 把中断保持开放,超过内核对未确认在途数据的重传预算——以及该连接的 keepalive 预算(仅当应用真的在该套接字上启用了 keepalive)。这一场景证明基准测试测的是恢复,而非仅仅一次瞬态闪烁的缺席。如果持续中断情形不在矩阵中,一次快速瞬态恢复就会掩盖一个对长寿命故障根本没有行为的设计。

如何断言每个场景而又不夸大

对每个场景,断言必须放在被测试机制上一层,并使用该层拥有的信号:

  • 数据路径: 在重定向 tracepoint 上计数。重定向文档列出了 xdp_redirect、xdp_redirect_err、xdp_redirect_map、xdp_redirect_map_err 和 xdp_devmap_xmit;devmap 发送失败由 xdp_devmap_xmit 的 err 字段承载——并不存在单独的 xdp_devmap_xmit_err tracepoint——其 bpftrace 示例正是从 xdp_redirect*_err 与 xdp_devmap_xmit 构建 errno 直方图来调试静默丢包。一个统计 xdp_redirect_err 的探针能区分"重定向失败"与"重定向成功且帧已投递",而吞吐量数字做不到。
  • 连接: 采样套接字状态与序号计数器,而不只是套接字是否存在。TCB 的 ESTABLISHED 状态、SND.NXT/RCV.NXT 的单调性、以及该流没有 RST,才是信号。一个存在但序号计数器已重置的套接字是新连接,不是被恢复的连接。
  • 应用: 让被标记操作端到端完成,并在调用方检查间隙、重复与错误。包计数不能替代这一点。

CPU 开销指标应当按场景、按层报告,而不是一个混合总数。重定向处理、TCP 恢复或重连,以及应用重放走的是不同代码路径,也可能在内核态与用户态之间转移工作。报告一个混合数字会掩盖真正开销在哪一层。

什么情况让基准测试失去意义

当故障注入与恢复断言位于同一层时,基准测试失去意义。具体而言:

  • 在数据路径注入故障,又只在数据路径断言恢复。只要接口回来,数据路径就会被读作"已恢复",无论连接发生了什么。
  • 报告"恢复延迟"却不说明它测量的是哪一层的第一个成功事件。没有层的延迟数字是一个自由变量。
  • 用"故障切换期间的丢包"作为唯一的丢包指标,而缺少按应用声明的重试/幂等语义做的应用层结果检查。内核可以无限丢包并重传,应用仍可能正确;反之,应用可以正确,而线路显示大量丢失。
  • 不点名所测试的存活机制。有未确认数据在途时,起作用的是有效的 tcp_retries2 边界;空闲连接会无限存活,除非该套接字启用了 keepalive。不点名机制,基准测试就无法把存活的连接与重新建立的连接区分开。

决定性规则与任何跨层恢复测试相同:恢复断言必须比故障注入高一层,指标必须以其所测量的层命名。

参考资料

当日社区讨论

本页回答的是一个真实的技术问题:它由本站持续监测的 eBPF 实践者社区提出,此处以匿名化形式转述。页面不携带任何参与者身份、账号、雇主、项目、渠道名称、消息链接、精确时间戳、私有拓扑、原始日志,或可被搜索回某一个人的措辞;本次发布也不伴随任何回复、反应、私信、关注、邀请或 moderation 动作。

这个保留问题来自一个受监测社区中一个允许归档渠道的可读归档资料。另一个归档支持的社区因预期渠道无法匹配而不可访问。本次尝试没有复核两个仅限浏览器访问的聊天社区、公开邮件列表归档和公开论坛。因此,这五个社区属于未覆盖,而不是“没有讨论”;本文不对其活跃情况作出判断。

一条与先前内容相关的已核实线索仍然有用。此前的一篇每日问答解释过为什么 eBPF 程序不能读取 bpf_tail_call() 的返回值:一个成功路径不返回的辅助函数,让"失败续行"而非返回码成为可观察事件。这正是本页应用在基准测试上的同一设计教训——可观察结果必须位于拥有它的那一层,即被测机制之上的一层。瞬时恢复基准测试会以多快的速度收敛到按层断言——把数据路径恢复、连接存活、应用可见的完成分开报告,而不是只报一个"数据包再次流动"的数字——仍是监测管线持续跟进的未决问题。

继续阅读

最后更新
2026年9月8日
首次发布
2026年9月8日
贡献者
Yunwei 123

这个页面有帮助吗?