
一、问题现象我前几年曾经为某运营商设计并落地一套UPF架构。当项目进入集成测试阶段硬件配置Intel Xeon Gold 双路CPU2×100G Intel E810网卡DPDK 20.11Linux Kernel 5.15系统架构如下设计目标单机100Gbps64字节报文双向转发实际压测结果指标目标值实际值Throughput100Gbps15GbpsPPS148Mpps22Mpps丢包率035%最诡异的是网卡没打满、内存也没打满但是吞吐量就是上不去。这类问题在高性能网关开发中极其常见。二、第一步定位CPU到底在干什么首先使用perf采样perf top热点函数45% rte_ring_enqueue_burst 18% rte_ring_dequeue_burst 12% rte_hash_lookup_bulk 8% rte_pktmbuf_alloc_bulk发现一个奇怪现象真正的数据包处理逻辑process_gtpu_packet()只占不到10%。CPU时间主要消耗在Ring Hash Mbuf这些基础设施上。说明问题不在业务逻辑。而在架构设计。三、Dispatcher成为瓶颈当前架构RX | V --------------- | Dispatcher | --------------- / / / / / / V V V V V V Worker WorkerDispatcher负责收包 解析TEID 计算Hash 决定Worker 入RingWorker负责GTP-U处理 PDR查找 FAR处理 转发看起来很合理。但问题来了。假设100Gbps 64Byte对应148.8 MppsDispatcher必须完成148.8M次TEID解析 148.8M次Hash 148.8M次Ring操作实际上Dispatcher已经成为整个系统的单点瓶颈。架构如下RX | V Dispatcher | ---------------- | | | | | | V V V V V V Worker Worker Worker所有流量必须经过Dispatcher。这是典型的串行入口 并行出口架构。四、缓存一致性风暴出现进一步观察perf c2c结果显示rte_ring_prod_tail rte_ring_cons_head大量Cache Line Bounce。原因多个Core同时访问Ring。例如Dispatcher ↓ Worker0 Dispatcher ↓ Worker1 Dispatcher ↓ Worker2Dispatcher不断修改prod_tailWorker不断修改cons_head导致Core0 Cache ↓ Invalidate ↓ Core3 Cache ↓ Invalidate ↓ Core0 Cache形成缓存乒乓。如下图此时CPU利用率看起来不高。但实际上大量周期浪费在MESI协议同步五、NUMA问题浮出水面继续检查dpdk-proc-info发现网卡: NUMA Node 0 Worker: NUMA Node 1即NIC -- Socket0 Worker -- Socket1每个数据包都发生PCIe DMA ↓ Socket0 Memory ↓ QPI/UPI ↓ Socket1 CPU跨NUMA访问。现代双路服务器本地访问80ns 跨路访问150~200ns看似只差100ns。但100Mpps情况下影响极其巨大。六、真正的问题线程模型设计错误经过分析发现系统采用的是RX ↓ Dispatcher ↓ Worker ↓ TX四阶段流水线。这种设计来自传统软件架构思想模块解耦 职责分离但在100G数据面场景下这是错误的。因为每经过一个阶段都会产生Ring切换 Cache失效 Core迁移例如RX Core ↓ Worker Core ↓ TX Core数据包生命周期中同一个mbuf被多个CPU访问。缓存命中率急剧下降。七、现代高性能网关的设计原则真正高性能的架构RX Queue0 → Core0 → TX Queue0 RX Queue1 → Core1 → TX Queue1 RX Queue2 → Core2 → TX Queue2如下即Run-To-Completion模型。特点一个包只属于一个CPURX 处理 转发 TX全部在同一个Core完成。避免Ring Lock Cache Bounce八、为什么VPP性能高VPP采用Vector Processing思想。不是1包处理一次而是32包 64包 128包一起处理。例如while(nb) { prefetch(pkt[i4]); process(pkt[i]); }CPU执行过程Load Load Load Load Compute Compute Compute形成流水线。减少Cache Miss Branch Miss现代CPU最怕等待内存而不是计算。九、重新设计UPF数据面经过重构后采用RSS ↓ N3 Worker ↓ F-TEID Hash ↓ PDR/FAR ↓ N6 TX原则原则1Dispatcher只做最轻量工作解析TEID 计算Worker禁止PDR查找 业务逻辑原则2Session固定归属WorkerTEID % WorkerNum保证同一UE 同一CPU避免状态同步。原则3NUMA绑定--socket-mem4096,4096Worker与网卡同NUMA。原则4批处理rte_eth_rx_burst()每次32~64包一起处理。原则5避免跨线程Ring能不用rte_ring就不用。因为一次Ring ≈几十到上百CPU Cycle100Mpps下代价巨大。十、优化结果优化前指标数值PPS22MThroughput15Gbps优化后指标数值PPS128MThroughput86Gbps继续优化Hugepage布局NUMA亲和PrefetchSIMD最终达到95~98Gbps接近线速。十一、高性能网关架构设计的本质很多开发者认为性能 算法实际上在100G时代真正决定性能的往往不是算法。而是缓存命中率 NUMA访问 线程模型 数据流向一个Hash查找可能只需要20ns但一次跨NUMA访问可能需要200ns一个业务逻辑可能只占10%而线程间Ring切换可能占50%因此高性能网关设计最重要的一条原则是不要让数据包在CPU之间旅行而要让CPU拥有数据包。当系统达到100Gbps甚至200Gbps时决定性能上限的已经不再是代码是否优雅而是缓存、NUMA和线程模型是否符合现代CPU架构。真正优秀的数据面设计往往不是增加更多线程而是减少数据包在不同线程之间的流动次数让一次缓存加载产生最大的价值。