第二讲:硬件基础与NUMA架构
大家好,欢迎来到第二讲。
今天聊的话题,是很多做网络优化的朋友容易忽略的——NUMA架构。说白了,就是你的CPU是怎么访问内存的。这事儿搞不清楚,你后面调优再努力,效果可能都要打折扣。
一、什么是NUMA?为什么它跟网络性能有关?
NUMA,全称Non-Uniform Memory Access,非一致性内存访问。名字挺绕口,但意思很简单:CPU访问不同位置的内存,速度不一样。
我刚开始接触Intel服务器时,一直以为所有内存对CPU来说都一样快。直到有一次,我在一台双路服务器上跑DPDK测试,发现同样的代码,换个CPU核心跑,延迟差了30%。当时我就懵了——代码没变啊?
后来一查,原来是NUMA的问题。你想想看,CPU访问自己“本地”的内存,走的是直连通道,延迟低、带宽高。但访问“远端”内存(另一个CPU的内存),就得绕路走QPI/UPI总线,延迟自然就上去了。
核心结论:网络数据包的处理,一定要尽量在同一个NUMA节点内完成。跨NUMA访问,性能损失至少在20%-40%。
二、Intel CPU的NUMA拓扑结构
我们来看看典型的Intel双路服务器长什么样。
| 组件 | 说明 |
|---|---|
| Socket 0 | CPU 0,包含物理核心、L3缓存、内存控制器 |
| Socket 1 | CPU 1,结构同上 |
| UPI总线 | 两个CPU之间的互联通道,负责跨NUMA通信 |
| 内存通道 | 每个CPU直连自己的DDR4/DDR5内存条 |
| PCIe Root Complex | 每个CPU有自己的PCIe控制器,网卡插在哪个CPU上很重要 |
嗯,这里要注意:网卡物理插在哪个PCIe槽上,就属于哪个CPU的NUMA域。如果你把网卡插在Socket 0的槽上,但你的应用程序跑在Socket 1的核心上,那每次收发包都要跨NUMA——性能直接腰斩。
三、怎么查看NUMA拓扑?
我个人习惯用这几个命令,简单直接:
# 查看NUMA节点数量
numactl --hardware
# 查看每个CPU核心属于哪个NUMA节点
lscpu | grep -i numa
# 查看网卡所在的NUMA节点
cat /sys/class/net/eth0/device/numa_node
举个例子,我手头一台服务器跑出来是这样的:
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23
node 0 size: 128 GB
node 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31
node 1 size: 128 GB
看到没?核心0-7和16-23在Node 0,核心8-15和24-31在Node 1。每个节点128GB内存。
小技巧:我建议你把网卡中断和应用程序都绑定到同一个NUMA节点上。比如网卡在Node 0,那就用Node 0的核心来处理网络包。
四、NUMA对网络性能的具体影响
咱们来量化一下。假设你有一张25G网卡,跑在双路服务器上。
场景一:本地NUMA访问
- 网卡在Node 0,应用程序也在Node 0
- 数据包从网卡到内存,再到CPU,全程走本地通道
- 延迟:约1-2微秒
- 吞吐量:接近线速
场景二:跨NUMA访问
- 网卡在Node 0,应用程序在Node 1
- 数据包先到Node 0的内存,然后通过UPI总线传到Node 1
- 延迟:增加30%-50%
- 吞吐量:下降20%-40%
我曾经帮一个客户排查问题,他们用DPDK做包处理,吞吐量死活上不去。我一看,网卡在Node 0,但DPDK的lcore全绑在Node 1上。调整之后,性能直接翻倍。你说这坑大不大?
避坑指南:我曾经见过有人把两个网卡分别插在两个CPU上,然后让一个应用程序同时处理两个网卡的流量。结果跨NUMA通信导致性能剧烈抖动。我的建议是:要么一个应用程序只处理一个NUMA节点上的网卡,要么用多进程架构,每个NUMA节点跑一个实例。
五、实际调优中的NUMA绑定策略
好了,理论说完了,咱们来点实操。怎么做NUMA绑定?
1. 中断绑定
网卡中断要绑定到同一个NUMA节点的核心上:
# 查看网卡中断号
cat /proc/interrupts | grep eth0
# 绑定中断到Node 0的核心(比如核心0-7)
echo 1 > /proc/irq/中断号/smp_affinity
2. 进程/线程绑定
用numactl启动你的网络应用:
# 绑定到Node 0,并在核心0-7上运行
numactl --cpunodebind=0 --membind=0 ./your_app
3. 内存分配策略
我个人习惯用--membind而不是--preferred。为什么?--membind强制所有内存分配都在指定节点,不会偷偷跑到远端去。而--preferred只是优先,如果本地内存不够,还是会用远端内存——这在网络场景下很要命。
六、多核场景下的NUMA注意事项
你想想看,现在服务器动不动就几十个核心。如果所有核心都在同一个NUMA节点上抢内存,那内存带宽就成了瓶颈。
我建议的做法是:
- 每个NUMA节点分配独立的网络处理任务
- 每个节点内,核心数不要超过内存通道数的4倍(经验值)
- 跨NUMA节点的数据交换,尽量用共享内存或消息队列,避免频繁的内存拷贝
举个例子,一台双路服务器,每路8个内存通道。那每个NUMA节点上,我最多用32个核心来处理网络包。再多的话,内存带宽就不够了,加核心反而没收益。
一句话总结:NUMA不是玄学,是物理限制。理解它,你就能避开90%的网络性能坑。
好了,这一讲就到这里。下一讲我们会深入聊聊网卡硬件——从寄存器到队列,看看数据包到底是怎么从网线走到CPU的。到时候见。