服务器NUMA架构是现代多路处理器的标准拓扑结构。在双路、四路服务器上,每个CPU拥有本地内存控制器,访问本地内存延迟低、带宽高,访问远端CPU的内存则需要经过QPI/UPI互联总线,延迟增加30%-100%。NUMA感知优化对数据库、虚拟化、高性能计算等内存密集型场景至关重要。
NUMA拓扑结构与节点内存分布查看
Linux系统提供多种工具查看NUMA拓扑。最基础的是lscpu命令:
lscpu | grep -i numa# 输出示例(双路服务器):# NUMA node(s): 2# NUMA node0 CPU(s): 0-23,48-71# NUMA node1 CPU(s): 24-47,72-95
node0包含物理核0-23和超线程核48-71,node1包含物理核24-47和超线程核72-95。每个节点管理本地内存,CPU访问本节点内存延迟约80ns,跨节点访问延迟约140ns。
查看各节点内存使用情况:
numactl --hardware# 输出示例:# available: 2 nodes (0-1)# node 0 cpus: 0 1 2 ... 23 48 49 ... 71# node 0 size: 65536 MB# node 0 free: 32768 MB# node 1 cpus: 24 25 ... 47 72 73 ... 95# node 1 size: 65536 MB# node 1 free: 28672 MB
查看进程的NUMA内存分布:
numastat -p $(pgrep -f mysqld)# 输出进程在各NUMA节点上的内存分配情况# Per-node process memory usage (in MBs)# Node 0 Node 1 Total# Mysqld 28672 4096 32768
上述输出表明MySQL进程在node0分配了28GB,node1分配了4GB,存在严重的跨节点内存访问。理想情况下,进程内存应集中在运行其CPU所在的节点。
numactl工具与CPU绑核内存绑定实践
numactl是NUMA优化的核心工具,可以在启动进程时指定CPU和内存亲和性。
常用绑定策略:
# 将进程绑定到node0的CPU,只从node0分配内存numactl --cpunodebind=0 --membind=0 /usr/local/mysql/bin/mysqld# 将进程绑定到指定CPU核,只从最近的NUMA节点分配内存numactl --physcpubind=0-15 --localalloc /usr/local/mysql/bin/mysqld# 交错分配内存(跨节点轮询),适合内存访问模式不固定的场景numactl --interleave=all /usr/local/nginx/sbin/nginx
各参数适用场景:
–membind=N:进程所有内存必须从节点N分配,节点N内存不足时分配失败。适合对延迟敏感的数据库进程。
–localalloc:优先从CPU所在节点分配本地内存,本地不足时回退到其他节点。适合大多数NUMA感知应用。
–interleave=all:跨所有节点交错分配内存页,将跨节点访问开销平均化。适合无法预测访问模式的场景,如虚拟化宿主机。
数据库场景NUMA优化配置
MySQL/PostgreSQL等数据库是典型的NUMA敏感型应用。默认情况下,Linux内存分配策略倾向于从当前CPU所在节点分配,但数据库的buffer pool可能在多个节点间分散,导致大量跨节点访问。
MySQL绑核优化方案:
# my.cnf中配置[mysqld]# 限制InnoDB buffer pool只从node0分配内存# 使用systemd配置NUMA策略[Service]ExecStart=/usr/bin/numactl --cpunodebind=0 --membind=0 /usr/local/mysql/bin/mysqld# 设置CPU亲和性,避免调度器跨节点迁移CPUAffinity=0-23 48-71# 禁止自动NUMA均衡NUMAPolicy=bind
Redis单线程模型对NUMA更敏感。Redis的主线程和后台线程应绑定到同一节点:
# redis.conf# 设置CPU亲和性为node0的物理核# 启动时绑定numactl --cpunodebind=0 --membind=0 redis-server /etc/redis/redis.conf# 在Redis运行时通过taskset调整taskset -cp 0-7 $(pgrep redis-server)
对于Redis Cluster多实例部署,将不同实例绑定到不同NUMA节点:
# 实例1绑定node0numactl --cpunodebind=0 --membind=0 redis-server --port 6379# 实例2绑定node1numactl --cpunodebind=1 --membind=1 redis-server --port 6380
虚拟化与容器场景NUMA调度策略
KVM虚拟机默认不感知NUMA拓扑,guest OS看到的内存可能分布在多个节点上。libvirt提供NUMA调优配置:
<domain type='kvm'> <vcpu placement='static'>8</vcpu> <cpu mode='host-passthrough'> <topology sockets='1' cores='4' threads='2'/> <numa> <cell id='0' cpus='0-7' memory='16777216' unit='KiB'/> </numa> </cpu> <vcpu cpuset='0-7'/> <memoryBacking> <locked/> </memoryBacking></domain>
locked标签锁定内存页,防止被swap换出,同时配合NUMA cell配置确保vCPU和对应内存在同一物理节点。
容器场景下,Docker通过–cpuset-cpus和–cpuset-mems实现NUMA绑定:
# 容器CPU绑定到node0的0-7核,内存从node0分配docker run --cpuset-cpus=0-7 --cpuset-mems=0 \ --name db-container -d postgres:16
Kubernetes通过CPU Manager和Device Manager实现NUMA感知调度,需要启用static CPU管理策略,配合Topology Manager的single-numa-node策略:
# /etc/kubernetes/kubelet-config.yamlcpuManagerPolicy: statictopologyManagerPolicy: single-numa-nodekubeReserved: cpu: "2000m" memory: "2Gi"
single-numa-node策略要求Pod的所有CPU和设备必须在同一个NUMA节点上,否则调度失败。这保证了延迟敏感型工作负载不会跨节点访问。
NUMA均衡器关闭与手动绑核方案对比
Linux内核默认开启NUMA自动均衡(numa_balancing),通过周期性扫描进程内存页,将远程访问频繁的页迁移到本地节点。但自动均衡本身带来CPU开销,且迁移时机不可控。
# 查看当前状态cat /proc/sys/kernel/numa_balancing# 0=关闭,1=开启# 临时关闭echo 0 > /proc/sys/kernel/numa_balancing# 永久关闭echo "kernel.numa_balancing = 0" >> /etc/sysctl.d/99-numa.confsysctl -p /etc/sysctl.d/99-numa.conf
对于已明确绑定策略的应用,关闭自动均衡后由管理员通过numactl手动控制,避免内核扫描带来的额外开销。对于未做绑定的通用服务器,保持自动均衡开启可以利用内核的页迁移机制改善性能。生产环境建议关闭自动均衡并配合numactl手动绑定,以获得更稳定可预测的延迟表现。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fu-wu-qi-numa-jia-gou-xing-neng-diao-you-nei-cun-qin-he/