为什么 DPDK 系统上线后会随机卡顿?——一次生产级 Latency Spike 的深度排障实录
一、问题背景实验室一切正常上线后却随机丢包项目背景一个基于 DPDK 的用户态 UPF 数据面。硬件环境双路 Xeon 100G Mellanox NIC NUMA 双节点系统架构RSS RX Queue ↓ Worker Pipeline ↓ Flow Lookup ↓ GTP-U Encap ↓ TX Queue实验室压测结果稳定 80 Gbps Latency 20us 无丢包看起来非常完美。但上线后问题出现了每隔几十分钟系统会突然Latency Spike现象时延从 20us 飙升到 2ms持续几十毫秒短暂丢包PPS 波动CPU 利用率无明显异常更致命的是无法稳定复现这类问题才是真正折磨 DPDK 工程师的噩梦。二、为什么这类问题极难定位因为传统性能问题持续存在例如CPU 跑满锁竞争吞吐不足这些问题perf 一跑就知道。但 Latency Spike本质是瞬时微观系统失稳问题可能只持续几十微秒 ~ 几十毫秒普通工具根本抓不到。三、第一阶段排查怀疑业务逻辑最开始大家都认为是不是 Flow Table 太慢于是优化 hash增加 prefetch减少 branch misscache line 对齐结果平均 PPS 更高了。但Spike 依然存在说明问题不在业务层。四、第二阶段怀疑 NUMA后来我们开始怀疑NUMA Remote Access因为网卡在NUMA0部分 worker在NUMA1于是我们做了CPU 绑核Queue 绑核HugePage NUMA 绑定mempool 分 NUMA结果平均 latency 降低了。但Spike 仍然存在只是频率降低了。说明NUMA 只是部分原因。五、真正的问题开始浮现Mempool Cache 抖动后来我们注意到一个奇怪现象Spike 出现时某些 corerte_mempool_get_bulk() 耗时突然暴涨正常几十 nsSpike 时数十 us这意味着mempool 分配异常六、DPDK Mempool 真正的隐藏机制很多人以为mempool 就是对象池其实远没那么简单。DPDK mempool有Per-Core Cache结构Core Local Cache ↓ Global Ring正常情况下core 从本地 cache 获取 mbuf。速度极快。但当 local cache 耗尽时。会批量从 Global Ring 获取而 Global Ring是多核共享这时候问题开始了。七、真正的隐性炸弹Remote Free后来我们终于发现系统采用RX Core 分配 mbuf TX Core 释放 mbuf这意味着mbuf 分配和释放 不在同一个 core于是大量 mbuf会跨核返回。这会导致Per-Core Cache 被污染进一步导致local cache missglobal ring 竞争cache bouncing最终在某个时间点大量 core同时访问Global Mempool Ring于是系统瞬间进入cache coherency storm这才是Latency Spike 的真正源头。八、为什么平均性能很好但 P99 极差因为绝大部分时间系统都运行在cache hot状态。但偶尔会触发cache collapse这时CPU 会LLC missremote accessmemory ordering stallatomic contention于是P99 latency 暴涨。九、为什么 perf 很难抓到因为Spike可能100ms 才出现一次而 perf通常统计平均值因此你会看到平均 perf 很漂亮但真实线上已经开始丢包。十、真正的杀手Memory Ordering后来我们继续深入。发现即使没有锁系统依然会stall原因很多 rte_ring 操作内部使用atomic CAS memory barrier例如__atomic_compare_exchange_n()这类指令会导致CPU pipeline flush尤其多核下代价巨大。十一、为什么“无锁”不等于“高性能”很多人误解lock-free 无开销实际上无锁数据结构往往会大量消耗 cache coherency特别是在高频 producer/consumer场景。因此真正高性能系统反而会减少共享而不是疯狂 lock-free十二、Burst 调度为什么也会引发 Spike后来。我们还发现Burst Size并不是越大越好。例如rte_eth_rx_burst(..., 32)改成128平均吞吐更高。但P99 latency 更差。为什么因为Burst 越大意味着包等待时间越长例如必须攒够128 个包才会进入下一阶段。于是小流量时会出现Head-of-Line Blocking这也是很多系统平均值很好 尾时延极差的原因。十三、另一个隐形问题PCIe Backpressure继续排查后。我们还发现某些 spike 时刻NIC TX QueueDescriptor 回收变慢原因PCIe 总线会出现瞬时拥塞尤其多队列 DMANUMA 跨节点IOMMU 开启场景。这会导致NIC 无法及时write-back descriptor于是TX recycle 延迟。进一步触发mempool starvation最后整个系统进入短暂雪崩。十四、IOMMU 为什么也会影响 DPDK很多人开启intel_iommuon是为了VFIO 安全隔离。但IOMMU本质也是地址转换DMA 时也会查 IOTLB如果IOTLB miss就会触发 page walk在高 PPS 下。这会形成额外 latency spike因此很多极致性能系统会关闭 IOMMU或者使用1GB HugePage减少 IOTLB 压力。十五、真正稳定的数据面系统怎么设计后来我们彻底重构系统。核心原则1. 包生命周期不跨核原则谁分配 谁释放避免Remote Free2. One Queue One Core避免共享队列3. 避免全局共享结构包括flow counterstatstimer wheel全部per-core 化4. 小 Burst 高频调度不要盲目追求最大吞吐而要平衡 tail latency5. NUMA 严格隔离包括NICQueueHugePageWorkermempool全部NUMA 对齐十六、DPDK 真正难的从来不是收包很多新手觉得DPDK 难点是rte_eth_rx_burst()其实完全不是。真正困难的是长期稳定低时延因为一旦进入100G千万 PPS多 NUMA多 Socket场景。系统瓶颈已经不是网络而是CPU 微架构 内存系统 PCIe Cache coherency十七、现代 DPDK 调优本质已经是微架构工程很多人以为网络开发优化的是协议实际上真正高端的数据面优化。最后都在优化cache lineprefetchmemory orderingTLBPCIe DMANUMA locality本质已经进入CPU 微架构领域十八、总结很多 DPDK 系统实验室跑得很好。上线后却会随机抖动latency spike瞬时丢包P99 爆炸根本原因通常并不是业务逻辑太慢而是系统进入了 cache/memory 失稳状态真正的大规模 DPDK 系统。最终比拼的不是谁 PPS 更高而是谁能长期稳定维持低 tail latency而这背后。拼的已经不是网络知识。而是CPU 微架构理解能力