一、问题现象

我前几年曾经为某运营商设计并落地一套UPF架构。当项目进入集成测试阶段:

硬件配置:

  • Intel Xeon Gold 双路CPU
  • 2×100G Intel E810网卡
  • DPDK 20.11
  • Linux Kernel 5.15

系统架构如下:

https://images.openai.com/static-rsc-4/kUr41DdcUAeim0OnBLjKjPtcUhrhqLQJCfUs9ipX08oJdSRyynY0dZf55uGMSe_vs3ou0tvSkRWCe3JqQqpJRb_hOK4HCBkPKnTNCL-_4RL-Hn00WkehV64Fly5j5bDW0gMYwEUp_l5z2lT6Mlousb9Wgre46qKXTFJSv3Cb-EX8J7lGNf8SjcVGlOzHQM03?purpose=fullsize

https://images.openai.com/static-rsc-4/dN924NZbewvdbxH-prvw3K_9RNrU8-X2lL9y10733lEJaGbC1fd0Q0V4CYQ6rrc2V9SDuWGn_nPYjfQHKpRLavvRmDyJMyuXsxAYau7QKWhrfQbkqqI2ajQavo7g4SDcZI5Q295fC3xOL1tV3NTUC3_znQ_42xnhLsHwNSF9BfyMVE4xfkGtaEhlKg-XSBHZ?purpose=fullsize

https://images.openai.com/static-rsc-4/Rlv7pbkfoBVvQ_WWgttrW2Qq_zjLUAG8pBlmKhd3i3XZl14gAVmGrQ1-3R6h0Br0oUCDH0UBSWPfA0fJWS5J_4w2dwfI5dN3FD28rqfg6BybQ5-E-z_LjEFJGoRIwKpB3EtKbufF7AdlJ1uz_QcjNjNhmW18DNIOwCzQIeepNE4MXhZt2cwgZVg3i5Xj69R2?purpose=fullsize

设计目标:

  • 单机100Gbps
  • 64字节报文
  • 双向转发

实际压测结果:

指标 目标值 实际值
Throughput 100Gbps 15Gbps
PPS 148Mpps 22Mpps
丢包率 0 35%

最诡异的是:网卡没打满、内存也没打满,但是吞吐量就是上不去。

这类问题在高性能网关开发中极其常见。


二、第一步定位: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 Worker

Dispatcher负责:

收包
解析TEID
计算Hash
决定Worker
入Ring

Worker负责:

GTP-U处理
PDR查找
FAR处理
转发

看起来很合理。

但问题来了。

假设:

100Gbps
64Byte

对应:

148.8 Mpps

Dispatcher必须完成:

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
    ↓
 Worker2

Dispatcher不断修改:

prod_tail

Worker不断修改:

cons_head

导致:

Core0 Cache
      ↓
Invalidate
      ↓
Core3 Cache
      ↓
Invalidate
      ↓
Core0 Cache

形成缓存乒乓。

如下图:

https://images.openai.com/static-rsc-4/hXWleJk2g30D3ZIMr8IN41PrkAn2bjKorwIPI4_D7omYXnU6JL0FvXwq2pTuoP5IZFjtTK-BUAb3SDwRp3EWOX9-1QUv3z_5i9coDehFaZ0QcCdxxGGDa9HpfwhQPWogSAr9KPpJE_RNcHaZRzaUjU7R9zqIMqLf8XGgRyUUBhGSr2r8dpTWEnnVSfttclzr?purpose=fullsize

https://images.openai.com/static-rsc-4/byWPb2iVyEH1r7L7KMJncBkUsPJkzNrARA7hLkIVL4VlWpnwOkEtFxSvYls-71bWhbo_9crePMxY-xuzxyza3mE4dNB2jMzo1QJ4DA00VG6dhOzrUZ9eYgvjqtLUosTqjc0gnmADy2qo1HbAP-xJcD6h2jTdeUA-kqKP4AGqodc8Sc7j7vvEwu6SNdtaM_fx?purpose=fullsize

https://images.openai.com/static-rsc-4/Ucc-oXuLyDtNxM-18gGBEko0LYM3PPmPfbb6x47IKz3Zji0wqcIeop6l_0wpxmlOSSYmNauJOOZX0WcJ9uzjLLPEhFUL_U4vxvcpesin66XogkPpIo2oULQ3smgKDx5dP3CAmqTyibGA3S6mJEXPzwuOIfEHv0mngEDT7y8tRFQ7Ancm44S0-Pu1qEPg-W6-?purpose=fullsize

此时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

如下:

https://images.openai.com/static-rsc-4/QAWTtGprdkURe8p6XTnwL53V9K-UAkAuMKY2AcESMSgjhX9B_PkM0ynN_Fg0AzRQoCnI624Cxw7I-J-zwxZ_56KQW9xfWTB78xKM7Pl0-SgqQHQYoWzGyw9m7-V2PgS8aFvqRk4UJcDfeUIjSp2x9fpSwU6RVp0Usg0OV3CX8wl67d2k5u4oSr7z9NafSQhK?purpose=fullsize

https://images.openai.com/static-rsc-4/IyrUMVVQuBQpqtPWnGSwrwBibRVZVu37HCVRkUhf7tJoep0SQuPTaaLO1slyssF9nakidaXRa8itss_MulAfMWUlrI6z8GGrXJlukeD8MwPH8FLLlWElvGhMn99uNimyTzRRhYuzb4tRrVD5h8cEldTsoMNIDXzfxBHUnYYPs49r4tSOy_timi2nnMWp6OIM?purpose=fullsize

https://images.openai.com/static-rsc-4/4HlOhl3E4I2vRoFLQAJbjwEDLeA53T-oz27EXa48KhgojwvnRY2r8I5vOZ_2PA0g4JNU48EjZcbSCtcwZSvh0r3UKO0HVsjBOKEhiDYYLGTMpBJhLESYts6Oc1WJy2DdNUkpKW9htwgoHRgFwA0kTLHGn2AhXozzCoM0NuRoj25x_gWGmEMMU_l0j_mjiq1u?purpose=fullsize

即:

Run-To-Completion

模型。

特点:

一个包只属于一个CPU

RX
处理
转发
TX

全部在同一个Core完成。

避免:

Ring
Lock
Cache Bounce

八、为什么VPP性能高

VPP采用:

Vector Processing

思想。

不是:

1包处理一次

而是:

32包
64包
128包

一起处理。

例如:

while(nb)
{
    prefetch(pkt[i+4]);

    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

原则:

原则1

Dispatcher只做最轻量工作

解析TEID
计算Worker

禁止:

PDR查找
业务逻辑

原则2

Session固定归属Worker

TEID % WorkerNum

保证:

同一UE
同一CPU

避免状态同步。


原则3

NUMA绑定

--socket-mem=4096,4096

Worker与网卡同NUMA。


原则4

批处理

rte_eth_rx_burst()

每次:


32~64包

一起处理。


原则5

避免跨线程Ring

能不用:

rte_ring

就不用。

因为:

一次Ring
≈几十到上百CPU Cycle

100Mpps下代价巨大。


十、优化结果

优化前:

指标 数值
PPS 22M
Throughput 15Gbps

优化后:

指标 数值
PPS 128M
Throughput 86Gbps

继续优化:

  • Hugepage布局
  • NUMA亲和
  • Prefetch
  • SIMD

最终达到:

95~98Gbps

接近线速。


十一、高性能网关架构设计的本质

很多开发者认为:

性能 = 算法

实际上在100G时代:

真正决定性能的往往不是算法。

而是:

缓存命中率
NUMA访问
线程模型
数据流向

一个Hash查找可能只需要:

20ns

但一次跨NUMA访问可能需要:

200ns

一个业务逻辑可能只占:

10%

而线程间Ring切换可能占:

50%

因此,高性能网关设计最重要的一条原则是:不要让数据包在CPU之间旅行,而要让CPU拥有数据包

当系统达到100Gbps甚至200Gbps时,决定性能上限的已经不再是代码是否优雅,而是缓存、NUMA和线程模型是否符合现代CPU架构。真正优秀的数据面设计,往往不是增加更多线程,而是减少数据包在不同线程之间的流动次数,让一次缓存加载产生最大的价值。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐