接上文一次RSS配置错误引发的百万级乱序事故——深入理解DPDK RSS、Toeplitz Hash、Queue Affinity与Flow一致性上-CSDN博客八、真正决定Queue归属的并不是Hash而是RETA很多DPDK开发者都有一个误区RSS Hash是多少就进入哪个Queue。实际上这是错误的。RSS真正的流程是五元组 │ ▼ Toeplitz Hash │ ▼ 32bit Hash Value │ ▼ RETARedirection Table │ ▼ RX Queue也就是说Hash只是索引RETA才是真正决定Queue归属的映射表。Intel大部分网卡82599、X710、XXV710、E810等都维护着一张硬件RETA。例如假设RSS Hash最终得到0x35并不会直接进入Queue53。而是RETA[0x35] ↓ Queue 7如果0x36 ↓ Queue 7那么两个完全不同的Flow也可能进入同一个Queue。因此Hash决定Flow是否一致。RETA决定负载是否均衡。这是两个完全不同的问题。九、问题终于出现了继续检查升级脚本。工程师发现了这样一段初始化代码。以前16 Queue RETA: 0 1 2 ... 15 循环映射升级以后32 Queue 重新生成RETA代码没有任何问题。但是。Dispatcher没有修改。系统架构仍然保持Queue0 ↓ Worker0 Queue1 ↓ Worker1 …… Queue15 ↓ Worker15新增出来Queue16~31全部重新绑定到了另一组CPU。理论上这也没有问题。因为RSS就是为了多Queue。可是真正的问题藏在Session里面。十、Session Owner 与 RSS Queue不是一回事系统的数据面采用的是典型状态机架构。每一个Session都有唯一Owner。例如Session100 ↓ Worker5以后。无论哪个方向的数据包。都必须交给Worker5因为Worker5维护Session状态TCP状态NAT状态StatisticsTimer所有运行时数据。这就是Session Affinity。也是所有高性能网关、UPF、防火墙共同遵循的原则。但是RSS并不知道Session。RSS只知道Hash。于是同一个Session。第一次RSS ↓ Queue5第二次RSS ↓ Queue21第三次RSS ↓ Queue7为什么因为升级以后RETA重新分布。虽然Hash没有变化。但是Hash对应的Queue已经发生变化。于是。Dispatcher收到的数据包开始来自不同Queue。如果软件设计是Queue ↓ Worker那么。同一个Flow。真的可能进入不同Worker。十一、为什么会出现TCP乱序这里很多人容易产生疑问。同一个Hash不是应该一直对应同一个Queue吗答案是对于同一个时间点来说是的。但是现场的问题并不是Hash变化。而是软件线程模型错误。很多项目直接这样设计Queue0 ↓ Worker0 Queue1 ↓ Worker1 ……Worker既负责收包又负责Session又负责Forward看起来效率最高。实际上。这种架构有一个巨大隐患。例如Queue5负载突然升高。开发人员为了均衡CPU。修改RETA。把部分Hash迁移Queue5 ↓ Queue21RSS认为很好。CPU更均衡了。但是Session仍然属于Worker5于是新的数据包开始进入Worker21旧的数据包仍然在Worker5结果就是同一TCP连接。两个CPU同时处理。TCP包自然开始乱序。十二、真正的问题不是RSS而是软件架构很多人一直认为RSS负责Flow Affinity实际上。RSS只能保证Flow ↓ Queue却不能保证Flow ↓ Session Owner因为Session属于软件。Queue属于硬件。二者没有任何关系。这也是为什么现代DPDK项目越来越少采用RSS ↓ Worker而更多采用RSS ↓ Dispatcher ↓ Session Lookup ↓ Owner WorkerDispatcher只负责收包ParserSession Lookup找到session*然后读取session-owner最后Enqueue ↓ 真正Owner Worker这样。无论RSS怎样变化。Session永远不会迁移。Flow一致性也不会被破坏。十三、PMU为什么会出现大量Cache Miss故障解决以后。工程师继续分析为什么吞吐下降这么明显Perf结果perf stat发现LLC Miss 增加2.8倍继续perf c2c结果更加明显。热点集中Session结构 Cache Line Bounce原因非常简单。以前Session100 ↓ Worker5整个生命周期。只有CPU5访问。Cache始终留在CPU5。升级以后CPU5 ↓ CPU21 ↓ CPU7 ↓ CPU5每一个包。Session Cache Line都要重新迁移。Intel MESI协议开始不停Invalidate ↓ Transfer ↓ ReloadCPU真正执行计算的时间没有增加。增加的是等待Cache一致性的时间。于是Backend Stall开始暴涨。CPU依旧100%。但是。有效指令数却下降了。吞吐自然下降。十四、为什么Linux内核很少遇到这个问题很多人会问Linux开启RSS几十年了。为什么很少听说这种问题原因就在于Linux网络栈本身就是RSS ↓ SoftIRQ ↓ 协议栈Socket最终并不绑定某一个CPU。TCP状态也允许不同CPU参与处理。再加上RPSRFSXPSFlow LimitLinux拥有完整的软件负载均衡机制。因此RSS只是第一层。而DPDK追求的是每秒数千万甚至上亿数据包的极致性能。任何锁原子操作Cache Bounce都会严重影响吞吐。因此。DPDK几乎都会采用Single Owner单拥有者Session只能属于一个Worker。这也是为什么现代高性能防火墙UPFvRouterDPIIPS几乎都采用Dispatcher ↓ Owner Worker而不是RSS ↓ Worker十五、生产环境的最佳实践经过这次事故团队重新梳理了整个数据面架构并总结出以下原则第一RSS只负责负载均衡不负责Session归属。RSS的职责是把流量尽可能均匀地分配到多个RX Queue提高网卡接收能力Session的拥有权必须由软件统一管理。第二不要把RX Queue与Worker做永久绑定。Queue只是数据进入系统的入口而不是业务处理线程的身份标识。任何依赖Queue Worker的设计在RETA调整、RSS Key修改或网卡更换时都可能埋下隐患。第三Dispatcher负责建立Flow与Session之间的联系。Dispatcher完成协议解析、Session查找以及Owner确认后再把数据包投递给真正的业务Worker保证状态始终由唯一线程维护。第四Worker永远不迁移Session。Session一旦创建完成其Owner应保持稳定。即使RETA发生变化、RX Queue数量调整或NIC升级也不应影响Session归属。第五把RSS当成硬件能力而不是软件架构。RSS只是提升接收并行度的工具它不能替代Dispatcher也不能解决状态一致性问题。十六、写在最后很多性能问题并不会表现为CPU不足、网卡丢包或算法退化。真正复杂的故障往往出现在硬件调度机制与软件架构设计之间的边界。RSS、Toeplitz Hash和RETA共同解决的是数据包如何进入系统Dispatcher、Session Owner和Worker解决的是数据包进入系统之后由谁负责处理。两者职责不同却必须彼此配合。这次事故最终恢复的关键并不是修改RSS参数也不是优化ACL或Hash算法而是重新建立了**RSS负责接收并行Dispatcher负责状态归属Worker负责唯一处理**这一清晰的数据面职责划分。对于设计高性能DPDK系统而言一个值得长期坚持的原则是硬件负责把包送进来软件负责把状态管起来。不要让RX Queue决定Session归属也不要让RSS替代软件架构。这也是当前主流高性能网络设备交换机、UPF、vRouter、防火墙等普遍采用Dispatcher Owner Worker架构的重要原因。只有将硬件并行能力与软件状态一致性解耦系统才能在扩容、升级和演进过程中始终保持稳定、高性能和可维护。