买了一台双路服务器,插了两颗 CPU、一大把内存,按理说性能应该翻倍,结果跑业务时发现还不如一台单路机器稳,某些场景下甚至更慢。这种情况多半是没搞清楚 NUMA 这回事:在双路及以上服务器里,内存不是"一视同仁"的,CPU 访问自己直连的内存和访问另一颗 CPU 挂的内存,速度能差出一大截。
这篇文章讲清楚 NUMA(Non-Uniform Memory Access,非统一内存访问)是什么、它在现代服务器里怎么组织、为什么它会成为性能陷阱,以及在 Linux 上用什么命令查看 NUMA 拓扑、怎么用 numactl、numastat 做排查和优化。读完你应该能判断:你的业务是不是跑在了"错的内存"上,跨节点访问是不是在拖后腿。
需要先明确适用场景:NUMA 只在多路(多颗物理 CPU)服务器上才有意义。单路服务器只有一个 NUMA 节点,不存在跨节点问题。所以如果你用的是单路机器,这篇更多是知识储备;如果是双路、四路、八路,那 NUMA 优化是绕不开的。
一、为什么会有"访问内存速度不一样"这回事
早年的多 CPU 系统是所有 CPU 通过一条共享总线访问同一块内存,谁访问都得排队,路越挤越慢。后来内存控制器(IMC)被集成进 CPU 内部,每颗 CPU 都直连自己那部分内存,访问速度才能提上去。
这样就形成了一个结构:每颗 CPU 加上它直连的那部分内存,组成一个 NUMA 节点(node)。CPU 访问本节点(local)的内存走的是直连通道,快;访问其他节点(remote)的内存,需要经过 CPU 之间的互联链路(Intel 的 UPI/QPI 或 AMD 的 Infinity Fabric)绕过去,慢。这个"绕过"就是"非统一"的含义。
跨节点访问的延迟差异有多大?以主流的双路服务器为例(经验值,具体因平台而异),本地内存访问延迟大约在 80 到 100 纳秒,跨节点访问大约在 130 到 180 纳秒,也就是慢 50% 到一倍左右。带宽上,跨节点还要和互联链路上的其他流量抢带宽,多路机器上互联链路很容易成为瓶颈。
对大多数普通业务,这点延迟差异感知不明显;但对内存带宽敏感、延迟敏感的场景——数据库、缓存、大数据计算、高频交易、虚拟机宿主——NUMA 的影响就非常显著。
二、查看 NUMA 拓扑的常用命令
排查第一步永远是"看清结构",别猜。
# 查看 NUMA 节点总览:每个节点有哪些 CPU、多少内存
numactl --hardware
# 输出示例:
# available: 2 nodes (0-1)
# node 0 cpus: 0 1 2 3 4 5 6 7
# node 0 size: 32768 MB
# node 0 free: 12000 MB
# node 1 cpus: 8 9 10 11 12 13 14 15
# node 1 size: 32768 MB
# node 1 free: 8000 MB
# node distances:
# node 0 1
# 0: 10 21
# 1: 21 10
# distances 里的 10 是本地访问相对值,21 表示跨节点访问约慢一倍
# 如果没装 numactl:
# RHEL/CentOS/Rocky
dnf install -y numactl
# Debian/Ubuntu
apt install -y numactl
# 通过 sysfs 查看更原始的信息
ls /sys/devices/system/node/
cat /sys/devices/system/node/node0/cpulist # node0 上有哪些 CPU
cat /sys/devices/system/node/node0/meminfo # node0 内存明细
# 用 lscpu 看 NUMA 概况
lscpu | grep -i numa
lscpu | grep -i "NUMA node"
# 查看进程的 NUMA 内存分布,-p 指定 PID
numastat -p
# 关注各节点的 local/remote 分配,remote 占比高就是问题
# 系统级统计
numastat
# numa_hit / numa_miss / numa_foreign 三个指标最关键:
# numa_hit 本节点命中,理想情况
# numa_miss 分配到了非预期节点
# numa_foreign 本节点的内存被其他节点抢走
# numa_miss 和 numa_foreign 高,说明内存分配策略有问题
三、NUMA 的几种内存策略
Linux 提供几种内存分配策略,通过 numactl 或 set_mempolicy 控制:
| 策略 | 名称 | 含义 | 适用场景 |
|---|---|---|---|
| 默认 | local | 分配到当前 CPU 所在节点 | 单进程独占、追求本地性 |
| interleave | 交错 | 轮流分配到各节点 | 大内存带宽型、避免倾斜 |
| bind | 绑定 | 只使用指定节点 | 强隔离、数据库专用节点 |
| preferred | 优先 | 优先指定节点,满则溢出 | 兼顾容量与本地性 |
选择逻辑很直接:如果业务需要最大内存带宽(比如大内存流式计算),用 --interleave=all 让带宽均摊;如果业务对延迟敏感且数据量不大,用 --membind 固定到本地节点;如果只是想避免某个节点被撑爆,用默认的 local 加监控即可。
# 示例:让命令在所有节点交错分配内存
numactl --interleave=all ./myapp
# 示例:把进程绑定到 node 0 的 CPU 和内存
numactl --cpunodebind=0 --membind=0 ./myapp
# 查看当前策略
numactl --show
四、常见性能问题的排查与优化
问题一:内存倾斜,一个节点吃满,另一个闲着。
numastat 里某个节点 free 很低、另一个大量空闲,说明分配不均。典型原因是内核的自动 NUMA 平衡(auto numa balancing)在某些负载下没起到作用,或者进程启动时全部分配到了启动 CPU 所在节点。
问题二:跨节点访问比例高。
numastat -p 显示 remote 占比很大,进程跑在 node0 的 CPU 上却在用 node1 的内存。解决办法是把进程和内存绑定到同一节点,或者开启自动 NUMA 平衡。
问题三:互联链路带宽被打满。
多路机器上,跨节点流量会挤占 UPI/Infinity Fabric 带宽。表现为整体带宽上不去。这时改交错策略反而可能更糟,应该做节点隔离。
# 查看内核自动 NUMA 平衡是否开启
cat /proc/sys/kernel/numa_balancing
# 1 表示开启(默认一般开启),0 表示关闭
# 临时开启(默认通常已开)
sysctl -w kernel.numa_balancing=1
# 永久生效写入 /etc/sysctl.d/99-numa.conf:kernel.numa_balancing=1
风险提示:关闭自动 NUMA 平衡或强制绑定节点,可能让本来均衡的负载变得倾斜,反而降低性能。建议先测再改,记录原值。回滚方式:sysctl -w kernel.numa_balancing=0 或改回配置文件。
问题四:数据库类应用的 NUMA 表现差。
很多数据库(比如某些版本的 MySQL、PostgreSQL、Redis)对 NUMA 默认策略敏感。常见做法是把数据库实例绑定到固定节点,或者用 numactl --interleave=all 启动,避免单个节点内存被吃光导致 swap。具体用哪种要压测确定。
# 启动服务时绑定(以 systemd 为例,放进 service 文件)
# ExecStart=/usr/bin/numactl --cpunodebind=0 --membind=0 /usr/bin/mysqld ...
# 修改后执行:
systemctl daemon-reload
systemctl restart mysqld # 注意:生产环境重启数据库有中断风险,需安排维护窗口
五、虚拟化场景下的 NUMA
虚拟化把 NUMA 问题放大了一层。宿主机有 NUMA 拓扑,虚拟机也有自己的"虚拟 NUMA"。如果虚机的 vCPU 和内存跨节点分布,虚机内部的本地访问在物理层可能变成跨节点。KVM/QEMU 提供了 vNUMA 支持,可以把物理拓扑透传给虚机,让虚机内的调度更贴近物理结构。
检查虚机 NUMA 感知:
# 虚机内查看是否感知 NUMA
numactl --hardware
# 若只在宿主机上能看到节点,虚机里没有,则需要开启 vNUMA 或做 vCPU 绑核
# 宿主机上把虚机 vCPU 绑到物理核(virsh 示例)
virsh vcpupin
virsh numatune
对云主机用户来说,宿主机拓扑一般不可见,能做的有限,通常是选择"独享型/计算优化型"实例来减少争抢。这里不涉及具体厂商型号,选型时关注"是否独占物理核"这个属性即可。
六、优化后的验证方法
改完策略要有验证,否则等于没改。
# 1. 看进程内存分布是否改善
numastat -p
# 关注 numa_hit 是否上升、numa_miss / numa_foreign 是否下降
# 2. 看各节点内存是否均衡
numactl --hardware | grep -A2 "node [0-9] free"
# 3. 用 numad 做动态建议(如已安装)
numad -i 10 -w -S 2>&1 | head
# numad 可以给出把进程绑定到哪个节点的建议
# 4. 应用层指标:对比优化前后的吞吐、P99 延迟
判断是否改善,最终要看业务指标,而不是只看 numastat 的数字。如果本地命中率上去了但业务没变快,说明 NUMA 不是瓶颈,别继续折腾。
还有一个容易被忽略的点:NUMA 影响不只在内存访问本身,还会通过"内存带宽竞争"间接影响性能。多路机器上,如果多个进程都要跨节点拉取大量数据,UPI/Infinity Fabric 这条互联链路会迅速成为瓶颈,表现可能是各核 CPU 都不高、numastat 里 remote 访问也不多,但业务吞吐就是上不去。这种情况下,正确的做法不是继续加 CPU,而是把相关进程和它依赖的内存放到同一节点上,减少跨节点流量。压测时可以用内存带宽测试工具(比如 stream 类工具或 mbw)确认互联链路是否吃紧,再决定隔离策略。
常见问题
Q:单路服务器需要关心 NUMA 吗?
基本不用。单颗物理 CPU 通常只有一个 NUMA 节点,不存在跨节点访问。除非是特殊的单路多 die 设计(部分 AMD 平台),才会有内部 NUMA 结构。
Q:怎么判断业务是否受 NUMA 影响?
先用 numastat -p 看是否有大量 remote 访问,再看业务是否是内存带宽/延迟敏感型(数据库、缓存、计算密集)。两个条件都满足,才值得做 NUMA 优化。
Q:numa_hit、numa_miss 多少算正常?
没有固定阈值。一般来说本地命中率越高越好,理想情况 numa_miss 和 numa_foreign 都很小。但如果业务本来就是内存带宽型,刻意交错反而会产生合理的"跨节点",要结合业务类型看。
Q:开启自动 NUMA 平衡会不会有副作用?
会有额外开销。自动平衡需要定期扫描内存页并迁移,会消耗一定的 CPU 和内存带宽。对已经做了静态绑定的应用,开启它意义不大,甚至可能干扰绑定效果。
Q:云服务器上看不到 NUMA 拓扑怎么办?
云主机的物理拓扑对用户不可见是常态。能做的就是选规格时关注实例类型,尽量选能保证物理核独享的规格;实在敏感的应用,考虑物理机或裸金属。
总结
NUMA 的本质是多路服务器里"内存访问速度不一致",本地快、跨节点慢(延迟差约 50% 到一倍,经验值)。单路机器基本无感,双路及以上必须关注。
排查固定套路:numactl --hardware 看拓扑和距离,numactl --show 看当前策略,numastat -p 看进程的实际内存分布,重点盯 numa_miss 和 numa_foreign。
优化手段分四类:绑定节点(--cpunodebind/--membind)、交错分配(--interleave=all)、开关自动 NUMA 平衡(numa_balancing)、虚拟化层做 vCPU 绑核与 vNUMA。
没有一种策略通吃所有业务,必须压测确定。改内核参数或服务启动方式前记录原值、留好回滚,重启数据库类服务要安排维护窗口。
所有优化最终以业务指标验收,别只盯着 numastat 的数字自嗨。
企业QQ咨询




