深入理解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,更省心:

  1. 未做精细绑定的、性能不敏感的应用
    特征:未绑定核心/内存,也没有使用 numactl 或 taskset 等控制手段。
    原因:线程会在节点之间自由调度,可能频繁访问远程内存,带来性能波动。
    关闭 NUMA 后:所有内存视为统一池,访问延迟相对稳定。
    示例:常见的 API 服务、业务中间层、后台任务服务。
  2. IO 密集型程序
    特征:CPU/内存使用率低,瓶颈在网络或磁盘。
    原因:NUMA 带来的内存分布优势对其几乎无意义,反而增加复杂性。
    示例:Nginx 等。
  3. 大内存、访问内存随机的应用
    特征:应用整体使用了大量内存(例如 200GB+),但访问分布随机、跨数据块跳跃访问,无法集中到单一NUMA 节点。
    原因:这些应用即使做了线程绑定,也很难做到内存绑定,因为数据分布天然是跨节点的;启用 NUMA 后,线程频繁访问“远程节点”数据,反而带来 延迟飙升与带宽瓶颈;NUMA balancing(自动页迁移)无法精准优化这些“随机+重度访问”的数据结构,反而增加系统开销。
    关闭 NUMA 优势:内存视为统一空间,数据不用迁移、分布自然均匀,性能波动显著减少;简化调优难度,不需关心 NUMA 页分布、节点亲和等细节。

📌 一句话总结:
如果你的程序 吃内存又“吃不准”内存位置,那就别为 NUMA 做无谓挣扎——关闭它,更稳定、更可靠。

六、写在最后

NUMA 调优既是一门“艺术”,也是“科学”。它不像频率那样直接影响性能,却在“延迟尾部”、“缓存命中率”这些关键指标上起到决定性作用。如果你正在构建一个高性能应用,理解 NUMA,是你必须迈过的一道门槛。

📬 欢迎关注VX公众号“Hankin-Liu的技术研究室”,持续分享信创、软件性能测试、调优、编程技巧、软件调试技巧相关内容,输出有价值、有沉淀的技术干货。

posted @ 2025-08-07 07:24  Hankin-Liu  阅读(161)  评论(0)    收藏  举报