一、问题出现了

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

系统采用:

DPDK + 多核Worker模型

主要功能:

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

整体架构如下:

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

实验室压测结果:

项目 数值
Worker 16
CPU频率 3.0GHz
Session 10万
PPS 350万
时延 <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
CPU 100%
PPS 80万
Session 220万

奇怪的地方出现了:

实验室:

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:

层级 大小
L1 32KB
L2 1MB
L3 30MB~60MB

而Session表:

560MB

远超L3容量。

https://images.openai.com/static-rsc-4/nGwtxhPD4aJN-MoltNNS9z3Pt1cS-FbzrMEmLyZwpi_jQbaoSvFMaRh6OnZprOQQeZMrNuxA5FGZzitYxU01gHcBQ1aKOr3YPPFxVgO5k03rbNyWqLW6dXVN9zcg38G_QH5thgUtUUdo-Lk-MkyZ48DJGhKdLSY07jIBiQLiyMtPVsikTMbHWw2UTkFDdenL?purpose=fullsize

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

https://images.openai.com/static-rsc-4/3ON_FzGNkyxaVsw6DPa_WD6IDfroDYFxfrA6SwRAAlEB7dvwd71rc3TqVb1K8JwEXpgqxUbBCePRbHxqqR1T3sBX_dPBDs9NNfgbOgiyl08FObnic2bFyEdiEJHtgrKSNsnwc3MlBVh6KA9edRGhcD734eBJ6jYZ0v2MmxprWe4R-BiNp3LWXGTcnOOp6epQ?purpose=fullsize

于是:

每个包到来:

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

https://images.openai.com/static-rsc-4/FKBST4cL7MHnUxtgem0UTIDl30jvE0-XQZQImQSo3m7-S-o2ql4iO_vU3aNUsjV5R1jqD6pqHcq6SG_4MvQUTwbH4eKpOtgLyyVmVxnMT95zdsMgLYHOXpxOaHdlVMe9O3BrlAgccmdpe4tkxzzwiSMfKh7k2h-vlodLT_6nKFk7V3bObzjIibIu8DbN2Efj?purpose=fullsize

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

https://images.openai.com/static-rsc-4/_qjKbIyX2jKy30gHVmaNtXr-X5hwuGyWomWvUqGl0iJ5aGM6xaUwZrcr8MnND85EPm_1oQFiO9tpWpJ6hwf50FQJw-vPKIseMaBnasPS3PCW6KfkPjjk-inFYjRMwFWHBtRWRizxarwZYVJ6wtn45ksbCMXviGWOZzdBY943sckHrOQRNTpYYpXo2UgnEcdN?purpose=fullsize

检查发现:

Worker运行在Socket0

Session分配在Socket1

于是:

跨NUMA访问

延迟从:

90ns

变成:

150ns+

PPS进一步下降。


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

原始架构:

          RX
           |
    ----------------
    |      |      |
   W0     W1     W2

          |
          |
   Global Session Table

统计信息:

session->pkt_cnt++;

多个Worker同时更新。

于是:

Cache Line竞争

开始出现。

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

https://images.openai.com/static-rsc-4/aNnvKRG7HntpnUtD41YK6d7psvCGPbAk-xxDTyKcn0ycyM1bcv7ujRFJtwbU7fTjVmjiCCQFx-uvJ-BGUJ_WmIvTyljYicmCD7CqIQOZ8PRgtMb8MK2dyIxVeL6pubegnluY71gaL3M_IzBpzhYPFdzjumBIyRVjMxBFyFzG6rkCTy-dL1UScLbz44O0iOu2?purpose=fullsize

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

即使没有显式加锁:

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

https://images.openai.com/static-rsc-4/4HYXbYtGpg-dBLPSnYqDxmq7j9M1HwMSHlS8k6SSA9Uor84kcR1nKBYXFZoOn0Kn4tl45znl4cL9vSo98dqxB_IzMvdoyO4aGzTgNSaHZQ-75wUFKN0oLwoGGs7tolCsBo_JBdELNpkF7V-JA1yHHWzb9M20YB309dqxubXXNACC65mnvcLgVfP_BUxIskru?purpose=fullsize

https://images.openai.com/static-rsc-4/hhYbGj3DKRxidz0dSxvQQnrR7jlX-2HvpF276Vwuvk1xh6egJ6XNvjR0yAK8ojPw4yg_-m7fM_YoSRvK0Rcq1yBxu2KhHabbZzxSUHp_SpMWwSmqqz6Fil-8D_cYvrtKIoVxyc8qhhHpcPmkYjApPS62OLpVef2sryVgdAeaN0u553cyf7MOEp4g-MPqm2Xs?purpose=fullsize

https://images.openai.com/static-rsc-4/81_GDpIuxKW2K_ppF75xyOmX3yPjIwaRbLF4-nqBHPtPAOfSi3a0pbS-0Er8z-k3bpROv3pVAuHJvFrVzuKV6PTZ4gYuyBaCYXNiqoWhe3npIvV54tlI0IGBFCHY7rLeYpVACViuOWJOAEGsncYiWpeNEqrGewoQFRc_pVodgegxbyYjD7kMguwf213_Ea0L?purpose=fullsize

这样:

同一个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

一批处理。

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

https://images.openai.com/static-rsc-4/i_xmEiEDGJOysbWHkUUWx-4lBUDBoAXa5tMXxrxKq9GbAkU6SZMbDd86RdF8Pxmkkzb4JZ3K_YijLOXn0XNkjVGWRFxZ7iZaaWbNexcGe7Y3-MQ5iuVYgKBBzKWyOAC1ardPzHMQarJVvA98p5AbcETF5wozmBiCd6h7vKY-TGfi0D-heDRjLcbboZW72Oho?purpose=fullsize

收益:

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

十四、优化后的结果

优化前:

指标 数值
Worker 16
CPU 100%
PPS 80万
Session 220万
Cycle/Packet 6000+

优化后:

指标 数值
Worker 16
CPU 100%
PPS 420万
Session 220万
Cycle/Packet 1100

可以看到:

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和下一代边缘网关架构设计的关键所在。

Logo

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

更多推荐