服务器NUMA架构通过将CPU和内存划分为多个节点来提升多路系统的扩展性,但跨节点内存访问带来的性能损耗对数据库和中间件等内存密集型应用构成显著挑战。NUMA架构性能调优的核心目标是将进程的内存分配与计算绑定在同一NUMA节点内,消除跨节点访问延迟。
NUMA架构原理与内存访问非对称性分析
在NUMA(Non-Uniform Memory Access)架构中,每个CPU Socket拥有本地内存控制器,访问本地内存延迟低、带宽高。跨Socket访问远端内存需要经过QPI或UPI互连链路,延迟增加约50%-100%,带宽下降30%-50%。以双路Intel Xeon为例,本地内存访问延迟约80-100ns,跨节点访问延迟可达140-200ns。
Linux内核默认采用First-Touch策略分配内存,即将页面分配给首次访问它的CPU所在节点。如果进程在Node 0启动但被调度到Node 1执行,所有内存访问都将变为跨节点访问。这种隐式性能损耗在高并发场景下尤为明显,数据库的缓冲池、Redis的数据集、Java的堆内存都可能受影响。
通过numactl –hardware命令可以查看系统的NUMA拓扑结构:
$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
node 0 size: 64512 MB
node 1 cpus: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
node 1 size: 65536 MB
node distances:
node 0 1
0: 10 21
1: 21 10
node distances矩阵中,10表示本地访问,21表示跨节点访问,数值越大延迟越高。这个比值直接反映跨节点访问的性能损耗程度。
numactl调度策略与CPU内存绑定配置
numactl提供三种内存分配策略:–membind将内存分配限制在指定节点,访问其他节点时触发SIGSEGV;–cpunodebind将进程绑定到指定CPU节点;–interleave采用轮询方式在多个节点间分配内存,适用于无法确定哪个节点更优的场景。
对于内存密集型应用,推荐使用–cpunodebind配合–membind实现CPU和内存的节点级绑定。以下是将PostgreSQL绑定到Node 0的配置示例:
# 查看PostgreSQL进程当前NUMA分布
numactl --show
cat /proc/$(pgrep -u postgres postgres | head -1)/status | grep Cpus_allowed
# 将PostgreSQL主进程绑定到Node 0
numactl --cpunodebind=0 --membind=0 /usr/pgsql-15/bin/postmaster -D /var/lib/pgsql/15/data
# 配置systemd服务实现NUMA绑定
cat > /etc/systemd/system/postgresql-15.service.d/numa.conf << 'EOF'
[Service]
NUMAPolicy=bind
NUMAAffinity=0
ExecStartPre=/bin/numactl --cpunodebind=0 --membind=0
EOF
systemctl daemon-reload
systemctl restart postgresql-15
使用systemd的NUMAPolicy指令是更规范的做法,在进程启动前就完成NUMA策略设置,避免进程运行后内存已经分配到错误节点的问题。NUMAPolicy支持bind、interleave、preferred三种模式,分别对应numactl的–membind、–interleave和–preferred参数。
PostgreSQL与Redis的NUMA绑定实战
PostgreSQL的shared_buffers区域是跨NUMA节点访问风险最高的部分。当PostgreSQL在Node 0启动时,shared buffers默认分配在Node 0,但如果后台进程被调度到Node 1执行,所有缓冲池访问都变为跨节点操作。解决方案是在PostgreSQL层面使用numactl启动,并配置CPU亲和性。
# PostgreSQL NUMA优化配置
# postgresql.conf关键参数
shared_buffers = 32GB
max_worker_processes = 16
# 确保worker进程不会跨节点调度
# 启动脚本加入numactl绑定
# 同时设置CPU亲和性,将所有PostgreSQL进程限制在Node 0的CPU上
numactl --cpunodebind=0 --membind=0 /usr/pgsql-15/bin/pg_ctl \
-D /var/lib/pgsql/15/data -l /var/log/postgresql/startup.log start
# 验证绑定效果
for pid in $(pgrep -u postgres); do
echo "PID $pid: $(cat /proc/$pid/numa_maps | grep -c "N0=") N0 pages, \
$(cat /proc/$pid/numa_maps | grep -c "N1=") N1 pages"
done
Redis作为单线程架构的内存数据库,NUMA优化同样关键。Redis的事件循环线程如果被调度到远端节点,每次内存访问都产生额外延迟,在百万QPS场景下累积损耗可达10%-15%。通过taskset将Redis进程绑定到特定CPU核心,配合numactl绑定内存节点,可消除跨节点访问:
# Redis NUMA优化
numactl --cpunodebind=0 --membind=0 redis-server /etc/redis/redis.conf
# 进一步绑定到具体核心(例如core 4)
taskset -c 4 redis-server /etc/redis/redis.conf
# 使用redis-benchmark验证优化效果
redis-benchmark -h 127.0.0.1 -p 6379 -t set,get -n 1000000 -c 50 -q
# 优化前: SET 234567 req/s
# 优化后: SET 278932 req/s (提升约18%)
MySQL InnoDB缓冲池NUMA优化方案
MySQL的InnoDB缓冲池在大内存配置下面临NUMA分配不均的问题。默认情况下,Linux内核在两路系统上会将缓冲池内存的50%分配到Node 0、50%分配到Node 1,但MySQL进程可能倾向于在一个节点上运行,导致另一个节点的缓冲池访问变为跨节点操作。MySQL 5.7及以上版本提供了innodb_numa_interleave参数,在缓冲池分配时启用interleave策略:
# my.cnf配置
[mysqld]
innodb_buffer_pool_size = 64G
innodb_numa_interleave = ON
innodb_buffer_pool_instances = 8
# 启用interleave后,缓冲池页面在两个NUMA节点间均匀分布
# 配合CPU亲和性设置,确保MySQL线程亲和到正确的节点
# 验证InnoDB缓冲池的NUMA分布
cat /proc/$(pidof mysqld)/numa_maps | awk '{
n0 += $4; gsub("N0=","",n0);
n1 += $6; gsub("N1=","",n1)
} END {
printf "Node 0: %d MB\n", n0*4096/1024/1024;
printf "Node 1: %d MB\n", n1*4096/1024/1024;
}'
跨节点内存访问检测与诊断工具
numastat是Linux内置的NUMA统计工具,可以查看每个节点的内存分配情况和跨节点访问统计。numastat -p pid命令显示指定进程在各节点的内存使用分布。
# 查看系统级NUMA统计
$ numastat
node0 node1
numa_hit 4567890123 4234567890
numa_miss 12345678 23456789
numa_foreign 567890 1234567
interleave_hit 1234567 1234567
local_node 4556789012 4222222222
other_node 12345678 23456789
# numa_hit: 本地节点命中次数
# numa_miss: 应该在本地但分配到远端的次数
# numa_foreign: 其他节点应该使用但分配到本地的次数
# other_node高说明存在大量跨节点访问
# 查看指定进程的NUMA内存分布
numastat -p $(pgrep -f postgres)
# 使用perf检测NUMA远程访问事件
perf stat -e node-loads,node-load-misses \
-p $(pgrep -f postgres) -- sleep 10
# 输出示例
# 1,234,567 node-loads
# 234,567 node-load-misses # 远端访问占比约19%
Intel VTune Profiler提供更精确的NUMA性能分析能力,通过硬件性能计数器采集LLC(Last Level Cache)缺失和远程内存访问事件。VTune的Memory Access分析类型可以精确定位代码中的跨节点访问热点,量化每次缓存缺失的内存访问延迟。
HugeTLB与TLB命中率优化配置
TLB(Translation Lookaside Buffer)是CPU缓存页表条目的硬件结构,TLB缺失会导致额外的内存访问。默认4KB页面在大内存应用下TLB命中率低,HugeTLB使用2MB或1GB大页减少页表条目数量,提升TLB命中率。在NUMA架构下,HugeTLB优化效果更加显著,因为每个TLB条目映射的内存更大,减少跨节点页表遍历次数。
# 查看当前HugeTLB配置
$ cat /proc/meminfo | grep Huge
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB
# 配置大页(以32GB = 16384个2MB大页为例)
# 针对NUMA节点分别配置
echo 8192 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 8192 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
# sysctl持久化配置
echo "vm.nr_hugepages = 16384" >> /etc/sysctl.conf
sysctl -p
# PostgreSQL启用大页
# postgresql.conf
# huge_pages = try # try或on
# Java应用启用大页
java -XX:+UseLargePages -Xmx32g -Xms32g -jar app.jar
# 透明大页(THP)配置
echo always > /sys/kernel/mm/transparent_hugepage/enabled
# 建议对数据库使用显式大页,关闭THP避免碎片
echo never > /sys/kernel/mm/transparent_hugepage/enabled
HugeTLB配置需要在应用启动前完成预留,否则可能出现大页分配失败导致应用无法启动。对于数据库场景,建议使用显式大页并关闭透明大页,避免THP的khugepaged线程在后台扫描和合并页面时产生性能抖动。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fu-wu-qi-numa-jia-gou-xing-neng-diao-you-yu-kua-jie-dian/