CPU始终100%,吞吐却只有别人十分之一?——一次高性能网关性能雪崩的完整分析
2026/6/6 8:51:33 网站建设 项目流程

一、问题出现了

某运营商边缘节点部署了一套用户面网关。

系统采用:

DPDK + 多核Worker模型

主要功能:

GTP-U解封装 ↓ Session查找 ↓ PDR/FAR匹配 ↓ 转发

整体架构如下:

N3 | | +-------------+ | Gateway | +-------------+ | | N6

实验室压测结果:

项目数值
Worker16
CPU频率3.0GHz
Session10万
PPS350万
时延<80us

结果非常理想。

上线一个月后却收到告警:

流量增长后出现丢包 业务时延明显升高

但查看系统状态时却发现一个奇怪现象:

top

结果:

16个Worker 全部100%

开发人员第一反应:CPU已经满了,需要增加服务器

但事实证明:

这完全是误判。


二、DPDK为什么CPU永远100%

先解释一个很多传统Linux开发者容易误解的问题。

Linux Socket程序:

while(1) { recvfrom(fd,...); }

没包时:

线程睡眠

CPU利用率下降。

而DPDK:

while(1) { nb_rx = rte_eth_rx_burst(...); process(); rte_eth_tx_burst(...); }

即使没有任何流量:

Worker仍在轮询RX Ring

因此:

CPU永远100%

这意味着:

CPU利用率 不能作为DPDK程序性能指标

真正重要的是:

PPS Cycle Per Packet Cache Miss

三、问题的第一现场

进一步统计:

指标数值
Worker数16
CPU100%
PPS80万
Session220万

奇怪的地方出现了:

实验室:

10万Session 350万PPS

现网:

220万Session 80万PPS

CPU同样100%。

为什么性能差了4倍?


四、perf告诉了真相

执行:

perf stat -e cycles,instructions,cache-misses

结果:

cycles 3.2e12 instructions 2.1e12 cache-misses 5.8e10

继续:

perf record perf report

热点函数:

rte_hash_lookup

占比:

42%

这意味着:

CPU大部分时间 都在查Session

五、Session表设计出了问题

Session结构:

struct upf_session { uint64_t seid; uint32_t teid; pdr_t pdr; far_t far; qer_t qer; statistics_t stat; timer_t timer; };

大小:

约256Byte

现网:

220万Session

总内存:2200000 × 256B ≈ 563MB

即:

约560MB

看起来不大。

但问题来了。


六、CPU真正害怕的东西:Cache Miss

现代Xeon:

层级大小
L132KB
L21MB
L330MB~60MB

而Session表:

560MB

远超L3容量。

于是:

每个包到来:

TEID ↓ Hash ↓ Session Lookup ↓ DRAM访问

CPU开始等待内存。


七、CPU其实一直在发呆

很多人认为:

CPU 100% 说明CPU很忙

实际上:

CPU可能正在:

等待内存返回数据

例如:

L1访问:

1ns

L2:

4ns

L3:

10ns

DRAM:

80~120ns

差距超过:

100倍

于是:

CPU看似100% 实际上大量Cycle被浪费

八、NUMA又补了一刀

服务器配置:

2 Socket

结构:

Socket0 ├─CPU 0~15 └─Memory0 Socket1 ├─CPU16~31 └─Memory1

检查发现:

Worker运行在Socket0 Session分配在Socket1

于是:

跨NUMA访问

延迟从:

90ns

变成:

150ns+

PPS进一步下降。


九、更隐蔽的问题:共享状态

原始架构:

RX | ---------------- | | | W0 W1 W2 | | Global Session Table

统计信息:

session->pkt_cnt++;

多个Worker同时更新。

于是:

Cache Line竞争

开始出现。

即使没有显式加锁:

MESI协议

也会导致:

Cache Line Ping-Pong

性能进一步恶化。


十、为什么Flow Affinity如此重要

高性能网关最重要原则:

同一Flow永远属于同一个Worker

例如:

worker_id = teid % worker_num;

架构变成:

Dispatcher | -------------------------------- | | | | | V V V V V W0 W1 W2 W3 W4

这样:

同一个TEID 固定进入同一个Worker

十一、状态本地化

进一步优化:

不要:

Global Session Table

改为:

Worker0 Session Pool Worker1 Session Pool Worker2 Session Pool Worker3 Session Pool

即:

State Ownership

原则:

谁处理Flow 谁拥有State

这样:

无锁 无竞争 无跨核同步

十二、Dispatcher + Worker架构

最终架构:

NIC | RX Queue | Dispatcher | ----------------------------------- | | | | | V V V V V Worker0 Worker1 Worker2 Worker3 Worker4 | | | | | | | | | | Session Session Session Session Session

Dispatcher职责:

解析GTP-U头 提取TEID Hash分发

Worker职责:

Session Lookup PDR匹配 FAR执行 QER处理 转发

Session仅属于本Worker。


十三、批处理带来的收益

不要:

process_one_packet();

要:

rte_eth_rx_burst();

例如:

32 Packet

一批处理。

收益:

  • 更高Cache命中率
  • 更少函数调用
  • 更好的流水线利用率
  • 更少Ring访问次数

十四、优化后的结果

优化前:

指标数值
Worker16
CPU100%
PPS80万
Session220万
Cycle/Packet6000+

优化后:

指标数值
Worker16
CPU100%
PPS420万
Session220万
Cycle/Packet1100

可以看到:

CPU始终100% 但吞吐提升超过5倍

这才是DPDK系统真实的优化效果。


十五、高性能网关设计原则

经过这次故障分析,可以总结出高性能网关设计的五条核心原则:

1. Flow Affinity

同一Flow固定进入同一Worker。

2. State Ownership

状态归属线程。

3. NUMA Awareness

网卡、CPU、内存保持同NUMA。

4. Batch Processing

尽可能批量处理数据包。

5. Cache First

设计首先考虑Cache命中率,而不是代码优雅性。


结语

许多开发者认为高性能网关的瓶颈来自CPU主频、核数或者算法复杂度。但在现代DPDK系统中,更常见的情况是:CPU已经100%运行,却有大量周期浪费在等待内存、跨NUMA访问和缓存一致性维护上。

从本质上说,高性能网关优化的核心并不是让CPU更忙,而是让CPU把每一个Cycle都花在真正的数据处理上。当Flow、State、Cache和NUMA形成统一设计时,系统才能从百万PPS平滑扩展到数百万甚至千万PPS,这也是UPF、CGNAT和下一代边缘网关架构设计的关键所在。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询