如何在香港裸金属服务器上通过NUMA优化与CPU亲和性配置提升高负载Web应用的处理能力?

昨天凌晨两点,香港机房突然传来告警:我们的主站 Web API 响应时间飙升,CPU 占用却并不满载。直觉告诉我这不是简单的资源瓶颈。调研之后我发现,部署在双路 Intel Xeon 服务器上的 Web 应用在高并发请求下存在 NUMA 跨节点内存访问导致的延迟抖动。同时,进程在系统中被随意调度,缺乏 CPU 亲和性的绑定,进一步放大了这种延迟。为了解决这个问题,我展开了一轮针对 NUMA 架构与 CPU 亲和性的系统级调优。以下是我在香港裸金属服务器上实践的全过程。
一、服务器硬件与应用架构概况
服务器位置:香港新界 Telehouse 高密度机柜
- 型号:Dell PowerEdge R7525
- CPU:2 x AMD EPYC 7543(共 64 核心,128 线程,2 NUMA 节点)
- 内存:512GB DDR4 ECC(按 NUMA 节点平均分布)
- 操作系统:Rocky Linux 8.9,内核 5.14
- Web应用:多进程 Nginx + uwsgi + Python Flask,使用大量本地内存缓存与Redis交互
二、NUMA 架构原理回顾与风险点分析
NUMA 架构特性:
- 每颗 CPU(或 Socket)对应一个 NUMA 节点,每个节点拥有本地内存。
- 进程如果访问非本地内存,会产生 远程内存访问延迟(remote access latency)。
- Linux 默认调度器未必将进程和其内存放在同一 NUMA 节点。
风险分析:
- 进程分布不均衡:多个 Nginx/uwsgi worker 被调度到不同 NUMA 节点,造成非本地内存访问。
- 内存未绑定节点:Flask 启动后从默认节点分配内存,不受控。
- 中断负载不平衡:NIC 中断集中在 NUMA-0 上,流量经过 CPU0 处理,但处理线程却在 NUMA-1。
三、实战步骤:NUMA与CPU亲和性联合调优
1.确定 NUMA 拓扑结构
lscpu | grep -i numa
numactl --hardware
输出示例:
available: 2 nodes (0-1)
node 0 cpus: 0-31
node 1 cpus: 32-63
node 0 size: 256000 MB
node 1 size: 256000 MB
2. 固定进程的 CPU 亲和性与内存节点(Nginx/uwsgi)
我将 Nginx 和 uWSGI 分别部署两组,每组绑定到一个 NUMA 节点:
# 绑定 Nginx + uWSGI 到 NUMA node 0(核心 0-31)
numactl --cpunodebind=0 --membind=0 /usr/sbin/nginx -c /etc/nginx/nginx-node0.conf
numactl --cpunodebind=0 --membind=0 /usr/local/bin/uwsgi --ini uwsgi-node0.ini &
# 绑定第二组到 NUMA node 1(核心 32-63)
numactl --cpunodebind=1 --membind=1 /usr/sbin/nginx -c /etc/nginx/nginx-node1.conf
numactl --cpunodebind=1 --membind=1 /usr/local/bin/uwsgi --ini uwsgi-node1.ini &
3. 优化 uwsgi 配置中的线程/进程分布
# uwsgi-node0.ini
processes = 16
threads = 2
cpu-affinity = 0-31
# uwsgi-node1.ini
processes = 16
threads = 2
cpu-affinity = 32-63
4. 配置 NIC 中断亲和性,减少 NUMA 跨节点 IO
# 查看中断绑定 CPU
cat /proc/interrupts | grep -i eth
# 使用 `irqbalance` 固定某些 NIC IRQ 到对应 NUMA 节点核心
echo 00000000,ffffffff > /proc/irq/XXX/smp_affinity
或者禁用 irqbalance,手动绑定:
systemctl stop irqbalance
for irq in $(grep eth0 /proc/interrupts | awk '{print $1}' | sed 's/://'); do
echo ffffffff > /proc/irq/$irq/smp_affinity
done
5. 设置内存页策略(防止页漂移)
# 避免页面随意迁移:madvise + numa balancing off
echo 0 > /proc/sys/kernel/numa_balancing
# 设置 uWSGI 进程为 interleave 分配(仅特定需要多 NUMA 模式)
numactl --interleave=all uwsgi ...
四、验证与性能对比分析
对比配置前后性能(wrk 测试):
| 测试项 | 默认调度器 | NUMA+CPU绑定优化 |
|---|---|---|
| 平均响应时间(ms) | 23.4 | 14.7 |
| P95响应(ms) | 57.8 | 21.2 |
| 最大并发支持数 | 6800 | 9800 |
| CPU使用率差异 | 不均衡 | 两节点均衡50% |
内核页迁移统计:
grep -i numa /proc/vmstat | grep -E 'migrations|local|remote'
# 优化后 remote_pages 值显著下降,remote->local 提高命中率
五、后续建议与补充优化方向
结合 cgroup v2 实现进程与NUMA节点的层级管控,确保资源配额不被打破。
内核参数调优:
sched_domain.cpu0.domain0.flags=524289
kernel.numa_balancing=0
结合业务指标联动:将 CPU 核心的温度与负载指标接入 Prometheus,动态微调 affinity。
容器化部署场景下:推荐使用 --cpuset-cpus 和 --cpuset-mems 控制容器的 NUMA 节点亲和性。
在这次优化过程中,我深刻体会到即使拥有强大硬件,如果忽视了 NUMA 结构与 CPU/内存调度机制,也会使 Web 应用性能陷入瓶颈。通过手动绑定进程到对应 NUMA 节点、合理配置 CPU 亲和性、优化中断分配,我们将原本抖动明显的高并发应用变得稳定且高效。这类调优并非一次性工作,而是应随业务扩展不断演进的持续工程。对于部署在香港的数据中心而言,尤其面向全球高并发访问,NUMA 与 CPU 拓扑感知的配置是不可或缺的基础设施优化环节。