做服务器测试和运维的同学,肯定都遇到过这种场景——机器配置看着挺豪华,16核32G 起步,但跑起来就是不够快。排查了半天,最后发现罪魁祸首是 NUMA 没配好。
今天这篇文章,是我之前做Linux Kernel 测试的时候使用的一些方法,我简单分享出来给大家参考。希望对你有所帮助。
一、先搞清楚:你的机器到底长啥样?
在动手调优之前,第一步是看清家底。下面这几个命令,建议每个运维同学都刻在肌肉记忆里:
| | |
|---|
lscpu | 快速概览 CPU 信息,包括 NUMA 节点数和 CPU 分布 | |
numactl -H | 详细 NUMA 硬件信息:CPU 列表、内存大小、节点间访问距离矩阵 | |
lstopo | | |
numastat | | |
重点看 numactl -H 的输出:
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7
node 0 size: 32768 MB
node 0 free: 16384 MB
node 1 cpus: 8 9 10 11 12 13 14 15
node 1 size: 32768 MB node 1 free: 20480 MB
node distances: node 0 1
0 : 10 21
1 : 21 10
🔑 关键解读:
- 系统有 2 个 NUMA 节点,节点 0 管 CPU 0~7,节点 1 管 CPU 8~15
- 距离矩阵是最关键的:节点 0 访问自己 = 10(本地),访问节点 1 = 21(跨节点延迟翻倍+)
这就是为什么跨节点访问内存会拖慢性能——数据要"绕路",而且这个开销远比你想象的大。
二、实战:5 种方式把程序"钉"在正确的地方
NUMA 调优的核心思想就一句话:让程序和它的数据待在同一个节点里。
📌 方式 1:启动时绑定(最常用)— numactl
这是将单个进程绑定到特定 NUMA 节点最直接的方法。
| |
|---|
--cpunodebind=N | |
--membind=N | |
--physcpubind=CPU | |
--interleave=all | |
✅ 最佳实践:CPU 和内存绑到同一个节点,效果最好。
🔧 MySQL 绑定示例(CPU + 内存全绑定):
CPU 和内存都锁定在节点 1 # numactl --cpunodebind=1 --membind=1 \ /usr/local/mysql/bin/mysqld \ --defaults-file=/etc/my.cnf
只绑定 CPU,内存使用本地分配策略 # numactl --cpunodebind=1 --localalloc \ /usr/local/mysql/bin/mysqld \ --defaults-file=/etc/my.cnf
📌 方式 2:运行中调整 — taskset
程序已经跑起来了?没问题,taskset 可以随时调整 CPU 亲和性:
绑定到单核 CPU 2 # taskset -pc 2 1234
绑定到多个离散核心 0、2、4 # taskset -pc 0,2,4 1234
绑定到一段连续核心 0~3 # taskset -pc 0-3 1234
💡 提示:taskset -pc 后面第一个参数是 CPU 列表,第二个参数是 PID。
📌 方式 3:批量管理 — cgroup (cpuset)
需要同时管控多个进程或容器?cgroup 的 cpuset 子系统是你的不二之选:
Kubernetes 用户应该对这个很熟悉——底层原理一模一样。
📌 方式 4:容器环境 — Docker 参数
Docker 原生支持 NUMA 绑定,一行命令搞定:
绑定到 CPU 核心 0 和 1 # docker run --cpuset-cpus="0,1" -d image_name 绑定到 NUMA 节点 0 的内存 # docker run --cpuset-mems="0" -d image_name
📌 方式 5:全局策略 — systemd 配置
不想每次手动绑定?可以在 systemd 层面设置全局 NUMA 策略:
# vim /etc/systemd/system.conf
[Manager]
NUMAPolicy=bind (或 interleave)
NUMAMask=0,1
# sudo systemctl daemon-reload sudo reboot
三、进阶:MySQL 的 NUMA 专项优化
MySQL 是 NUMA 调优的重灾区——数据库对内存访问延迟极度敏感。这里给两个经过验证的方案:
方案 A:内存交错模式(Interleave)
适合 MySQL 这种大内存、多线程的场景,让内存均匀分布在所有节点:
# 编辑 /usr/bin/mysqld_safe
# 找到启动 mysqld 的那一行,在前面加上: numactl --interleave=all
# 重启 MySQL 生效
方案 B:my.cnf 直接配置(推荐)
MySQL 5.6.27 / 5.7.9 以上版本原生支持:
# my.cnf 中添加
[mysqld] innodb_numa_interleave = ON
✅ 不用改启动脚本,干净利落。
四、踩过坑才知道的注意事项
⚠️ 避免过度绑定
绑得太死,一旦目标 CPU 繁忙,进程无法迁移到空闲核心,反而会造成排队等待。适度绑定,留有余量。
🐳 容器场景要注意
容器内看到的 CPU 编号和宿主机可能不一致,--cpuset-mems 的节点 ID 要对应宿主机的实际拓扑,别想当然。
☁️ 云虚拟机要小心
云 VM 的 NUMA 拓扑和物理宿主机往往不一致,生产环境中直接 numa=off 反而可能是更稳妥的选择。
🔧 BIOS 别忘了关节能
做 NUMA 优化前,先去 BIOS 把 Node Interleaving 关掉,再把 C1E / C States 这些节能模式禁了。不然 CPU 频率忽高忽低,调了也白调。
五、怎么确认生效了?验证清单
| | |
|---|
| numastat -p <PID> | |
| numactl --show | |
| vmstat / perf | |
觉得有用?转发给你的运维或者服务器测试小伙伴吧 🚀