深入理解NUMA:为什么你的服务在多核服务器上不稳定?
一、引言
在现代多核系统中,NUMA(非一致性内存访问)架构越来越普遍。合理配置 NUMA 策略,能够显著提升系统性能,但也可能踩坑。如果你曾在配置 BIOS 或内核参数时,纠结过是否该开启 NUMA,这篇文章或许能帮你做出更理性的选择。
二、什么是 NUMA?
NUMA(Non-Uniform Memory Access)是一种内存架构设计,适用于多处理器系统。在 NUMA 系统中,每个 CPU(更准确地说是 NUMA 节点)拥有自己“本地”的内存,访问这部分内存的延迟较低。而访问其他节点的“远程”内存则需跨 NUMA 总线,延迟和带宽都较差。
举个例子,假如你的服务器有两个 CPU,每个 CPU 配有本地的内存控制器和一块独立的内存。当线程 A 运行在 CPU0 上并访问 CPU1 的内存时,会发生所谓的“远程访问”。
三、大多数人忽略了:NUMA 的开关,竟影响系统性能这么大!
在我们帮多个团队做系统性能调优的过程中,发现一个被广泛忽略的配置项:NUMA 是否启用。
很多工程师甚至都不知道系统默认是 NUMA 模式,也从未在 BIOS 或内核中关注过它。但事实上,这个开关的状态,对性能的影响可能比你换一个高主频 CPU 还大!
❌ 常见误区:
- “NUMA 不是大模型才需要关心的吗?”
- “我们只是跑个中间件,关 NUMA 开 NUMA 没区别吧?”
- “Linux 会自动帮我优化亲和性,我不用管 NUMA 吧?”
错!实际上很多时候,正是“自动优化”带来的页迁移、线程调度失控,导致性能出现“诡异抖动”。
🔍 背后原理:NUMA 不只是“内存布局”那么简单
当 NUMA 启用时,系统会按“物理节点”划分内存,每个 CPU 访问本地内存延迟低,访问其他节点的内存延迟高、带宽也低。
例如,在典型的多路服务器(例如双路 NUMA 架构)中,CPU 访问远程节点内存的延迟,通常是访问本地节点内存的 1.5~2 倍以上。
这意味着,如果线程未绑定好所在核心或其访问的数据未合理分布,性能可能因为频繁跨节点访问而大幅波动。
同时,操作系统在 NUMA 模式下,会尝试让线程优先访问本地内存。但:
- 如果线程被调度到了另一个 NUMA 节点(例如由 CFS 调度器迁移);
- 如果内存页自动迁移失败;
- 如果应用本身无 CPU/内存亲和性设置;
那么程序就会频繁地访问“远程内存”,从而带来严重性能波动。
✅ 关闭 NUMA 呢?
系统会把所有内存视为一个统一的池,不再考虑本地/远程区别,线程无论在哪个 CPU 上运行,访问哪块内存的开销都差不多。
虽然理论上可能牺牲部分“本地访问优势”,但在调度不精细、线程迁移频繁的通用业务中,这种一致性反而更稳定、更省心。
开启 vs 关闭 NUMA:系统行为对比
行为差异 | NUMA 开启 | NUMA 关闭(UMA 模式) |
---|---|---|
内存分配 | 默认分配到本地节点(但依赖调度策略) | 所有内存视为统一池,随机分配 |
内存访问延迟 | 本地访问低延迟,跨节点高延迟 | 所有访问延迟一致但略高 |
NUMA 亲和性支持 | 可配合 numactl 控制内存与 CPU 亲和 |
numactl 无效 |
性能波动 | 亲和性合理则高性能;否则抖动严重 | 性能稳定但整体偏低 |
透明大页(THP)分配行为 | 可能跨 NUMA 节点,需配合策略优化 | 行为一致,分布平均 |
可用优化策略 | 支持自动/手动优化,如 CPU 绑核、内存绑定 | 无需太多配置,易部署 |
🔧 小结
开启 NUMA ≠ 性能更好,只有在你明确控制了线程绑定+内存分布的前提下,NUMA 才能真正发挥作用。否则,它可能正是你系统中“那条隐秘的性能地雷”。
四、哪些应用适合开启 NUMA?
只有「控制感」强的应用,才能驾驭 NUMA。虽然 NUMA 提供了本地内存访问的优势,但它并不是一个“自动生效”的加速器。要让 NUMA 成为性能提升的利器,前提是:你必须对线程运行在哪个核、内存分配在哪个节点,有明确控制能力。
换句话说,NUMA 适合的是“你能掌控”的那部分系统:能指定线程在哪里运行、数据在哪里分配的场景。如果做不到绑定控制,虽然 NUMA 仍能启用,程序也能正常运行,但是,不仅无法发挥 NUMA 架构的性能优势,反而可能因为访问不确定性,导致性能抖动甚至下降。
✅ 典型适合开启 NUMA 的场景
- 低延迟金融交易系统
应用特征:极致追求微秒级响应,如撮合引擎、行情分发等。且要求性能稳定,不能有较大抖动。
NUMA 策略:线程绑定在固定的核上或同一共享L3缓存的单元内(如AMD架构的同一CCX),数据结构预热在本地节点内存中。 - 高吞吐中间件
应用特征:频繁访问内存和缓存结构、高吞吐。如 Redis、Kafka。
NUMA 策略:将核心工作线程与内存绑定在同一个NUMA下,避免热点页跨节点访问。 - 虚拟化 / 容器环境下的资源隔离
应用特征:KVM/QEMU 等虚拟机,或容器调度器(如 Kubernetes)部署在物理多 NUMA 节点上。
NUMA 策略:将 VM / 容器绑定到单一 NUMA 节点,防止资源漂移。
五、哪些应用更适合关闭 NUMA?
并不是所有场景都能“玩得转” NUMA 架构。事实上,在很多通用型应用中,开启 NUMA 不仅无法提升性能,反而可能带来不可预测的问题:
- 页频繁迁移(numa balancing 导致的)
- 调度跨节点(线程漂移带来的远程内存访问)
- 内存碎片、分布混乱,影响缓存命中率
- 尾部延迟不稳定
✅ 以下几类应用,建议关闭 NUMA,更省心:
- 未做精细绑定的、性能不敏感的应用
特征:未绑定核心/内存,也没有使用 numactl 或 taskset 等控制手段。
原因:线程会在节点之间自由调度,可能频繁访问远程内存,带来性能波动。
关闭 NUMA 后:所有内存视为统一池,访问延迟相对稳定。
示例:常见的 API 服务、业务中间层、后台任务服务。 - IO 密集型程序
特征:CPU/内存使用率低,瓶颈在网络或磁盘。
原因:NUMA 带来的内存分布优势对其几乎无意义,反而增加复杂性。
示例:Nginx 等。 - 大内存、访问内存随机的应用
特征:应用整体使用了大量内存(例如 200GB+),但访问分布随机、跨数据块跳跃访问,无法集中到单一NUMA 节点。
原因:这些应用即使做了线程绑定,也很难做到内存绑定,因为数据分布天然是跨节点的;启用 NUMA 后,线程频繁访问“远程节点”数据,反而带来 延迟飙升与带宽瓶颈;NUMA balancing(自动页迁移)无法精准优化这些“随机+重度访问”的数据结构,反而增加系统开销。
关闭 NUMA 优势:内存视为统一空间,数据不用迁移、分布自然均匀,性能波动显著减少;简化调优难度,不需关心 NUMA 页分布、节点亲和等细节。
📌 一句话总结:
如果你的程序 吃内存又“吃不准”内存位置,那就别为 NUMA 做无谓挣扎——关闭它,更稳定、更可靠。
六、写在最后
NUMA 调优既是一门“艺术”,也是“科学”。它不像频率那样直接影响性能,却在“延迟尾部”、“缓存命中率”这些关键指标上起到决定性作用。如果你正在构建一个高性能应用,理解 NUMA,是你必须迈过的一道门槛。
📬 欢迎关注VX公众号“Hankin-Liu的技术研究室”,持续分享信创、软件性能测试、调优、编程技巧、软件调试技巧相关内容,输出有价值、有沉淀的技术干货。