上一篇 下一篇 分享链接 返回 返回顶部

香港服务器上的Windows Server 2016如何配置NUMA节点与CPU亲和性,提升虚拟化环境的整体性能?

发布人:Minchunlin 发布时间:2025-08-18 09:43 阅读量:981


凌晨两点,香港MEGA-i机房的冷通道依旧轰鸣不止,空气里混杂着冷气和金属的味道。值班的同事打了个哈欠,把工牌甩在胸前,我们一起推着那台刚下货的 HPE 双路服务器走进 42U 的机柜位。四周都是闪烁的指示灯和低频的风扇声,像是某种暗夜里的心跳。插上两根 10G 光纤、两根 PDU,确认指示灯全亮,电源一合,iLO 画面在控制台上缓缓浮现。熟悉的 Windows Server 2016 Datacenter 登录界面出现的那一刻,我心里清楚:真正的工作才刚开始。接下来的几个小时,我要在这台机器上把 NUMA 节点和 CPU 亲和性一一梳理清楚,在 Hyper-V 上跑出属于它的最大性能。

1. 场景与硬件基线

我先把环境交代清楚,这样你能代入每一步的意义,也便于和你自己的环境对照。

机房/网络:香港沙田→港岛 MEGA‑i 互联;ToR 交换机 10G,LACP×2 聚合到上联。

主机:HPE ProLiant DL380 Gen10,双路 Intel Xeon Gold 6130(2×16C/32T,2.1GHz),共 32C/64T;内存 384GB(12×32GB,6 通道/路,2 个 NUMA 节点);系统盘 RAID1(2×480GB SATA SSD),数据盘 RAID10(8×1.92TB NVMe U.2,经 HBA 直通给部分 VM)。

系统:Windows Server 2016 Datacenter(Hyper‑V 角色 + Failover Clustering),补丁打齐;BIOS 固件与 NIC 驱动为近两季的稳定版本。

来跑的 VM(简化):

DB‑HK‑01:Windows Server 2019 + SQL Server,16 vCPU / 96GB RAM,I/O 重,延迟敏感。

WEB‑HK‑01/02:IIS + .NET Core,4 vCPU / 8GB RAM,带 HAProxy;吞吐优先。

LOG‑HK‑01:Linux(RHEL 8),8 vCPU / 32GB RAM,ELK 节点,写多读多。

目标:把延迟敏感与吞吐敏感的 VM 分开跑到各自的物理 NUMA 节点上,尽量保证“本地内存访问”,并用 CPU 分组/亲和策略减少抖动;同时保留一定弹性给 Web/Log 这种可伸缩工作负载。

2. 上架后的 BIOS/UEFI 准备

现实里,很多“莫名其妙”的性能问题,最后都指向 BIOS 设置。

关闭 Node Interleaving(有些厂商叫 Memory Interleaving):必须禁用,不然 NUMA 会被打平,Hyper‑V 看不到真实 NUMA 拓扑。

Hyper‑Threading:保持开启(默认开)。

Virtualization:VT‑x/VT‑d、SR‑IOV 打开。

电源策略:OS Controlled / Performance。Windows 下再配合“高性能”电源计划。

做完这些再装系统或上线 Hyper‑V,省很多返工。

3. 装完系统第一件事:摸清 NUMA 家底

开机进系统后,我先用 PowerShell 把 NUMA 节点、可用内存、逻辑处理器数目摸清:

# 查看宿主 NUMA 拓扑(Hyper-V 模块)
Import-Module Hyper-V
Get-VMHostNumaNode | Format-Table NodeIndex, MemoryAvailable, MemoryTotal, LogicalProcessorCount

#(可选)看每个节点上 LP 的分布(需要 cpugroups.exe,后文会讲)

通常我会把结果抄到一个小表里,方便后面的容量规划:

NodeIndex LogicalProcessorCount MemoryTotal(GB) MemoryAvailable(GB)
0 32 192 170
1 32 192 168

Tips:如果这里看到的是 1 个节点,十有八九是 BIOS 开了 Node Interleaving,或者你用的是单路 CPU。

4. 关乎成败的两个开关:NUMA Spanning 与 vNUMA

Hyper‑V 下有两个必须理解的概念:

NUMA Spanning(主机级):允许 VM 使用跨 NUMA 节点的内存/CPU 资源;默认通常是 启用。为了追求本地访问延迟,我们会在关键 VM 稳定后将其 禁用,但要评估内存碎片和启动风险。

vNUMA(虚拟机级):把宿主的 NUMA 拓扑“映射”给 VM,让来宾 OS 的调度器也能做本地化决策。注意:vNUMA 与动态内存(Dynamic Memory)不能同时用,启用动态内存会让来宾只看到一个虚拟 NUMA 节点。

我一般这样做:

#(阶段一)上架调试期:保留 Spanning,保证 VM 都能顺利启动
Set-VMHost -NumaSpanningEnabled $true

#(阶段二)进入稳定期:对关键宿主批量关闭 Spanning,让 VM 尽量局限在单一 NUMA
Set-VMHost -NumaSpanningEnabled $false

经验语:关掉 Spanning 后,如果某个 VM 启不来,不是 Bug,是因为该节点上没攒够它需要的“连续可用内存”。要么挪 VM,要么把内存回收碎片整理(停机窗口),要么给它调小一点。

随后为需要 vNUMA 的 VM 关掉动态内存,并设置合适的 vCPU/拓扑:

# 以 DB‑HK‑01 为例:
Set-VMMemory -VMName "DB-HK-01" -DynamicMemoryEnabled $false -StartupBytes 96GB
Set-VMProcessor -VMName "DB-HK-01" -Count 16 `
  -MaximumCountPerNumaNode 8 `
  -MaximumCountPerNumaSocket 8

注:-MaximumCountPerNumaNode / -MaximumCountPerNumaSocket 用来约束 Hyper‑V 替你构造的 vNUMA 拓扑(常见做法是让每个 vNUMA 节点承载不超过宿主单节点可用的核心数)。

5. CPU 亲和性的现实可行路:CPU Groups(WS2016 引入)

很多人以为 Hyper‑V 可以像 vSphere 那样直接给单个 VM 设“CPU 亲和性”。严格说,Hyper‑V 没有直观的“把 VM 钉在某几个核”这一 GUI/PowerShell 开关;微软给的解法是:CPU Groups。

我把宿主的 LP(逻辑处理器)按 NUMA 节点分组,给不同业务(DB / WEB / LOG)建不同 Group;

给组设置 Group Cap(上限),同时把组的 LP 亲和列表限定在某个 NUMA 节点;

最后把 VM 绑定到对应的 CPU Group。这样既实现了“亲和性”(VM 只在那组 LP 上跑),也实现了资源上限隔离。

工具:微软提供的 cpugroups.exe(Host Compute Service 接口的 CLI)。

我一般的分配策略

Group‑DB:LP 0‑15(NUMA 0 的一半核心,含超线程配对),Cap 100%

Group‑WEB:LP 16‑31(NUMA 0 的另一半),Cap 70%

Group‑LOG:LP 32‑63(NUMA 1 全部),Cap 90%

操作步骤(示例):

# 1)查看 CPU/NUMA 拓扑(确认每个 LP 属于哪个 Node)
cpugroups.exe GetCpuTopology

# 2)创建组并设置 LP 亲和
cpugroups.exe CreateGroup /GroupId:11111111-1111-1111-1111-111111111001 /GroupAffinity:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
cpugroups.exe CreateGroup /GroupId:11111111-1111-1111-1111-111111111002 /GroupAffinity:16-31
cpugroups.exe CreateGroup /GroupId:11111111-1111-1111-1111-111111111003 /GroupAffinity:32-63

# 3)设置各组 Cap(单位是 1/65536 的百分比;32768=50%)
cpugroups.exe SetGroupProperty /GroupId:...001 /CpuCap:65536   # 100%
cpugroups.exe SetGroupProperty /GroupId:...002 /CpuCap:45875   # 70%
cpugroups.exe SetGroupProperty /GroupId:...003 /CpuCap:58982   # 90%

# 4)把 VM 绑定到组(示例 VM 名称需实际存在)
cpugroups.exe SetVmGroup /VmName:DB-HK-01   /GroupId:...001
cpugroups.exe SetVmGroup /VmName:WEB-HK-01  /GroupId:...002
cpugroups.exe SetVmGroup /VmName:WEB-HK-02  /GroupId:...002
cpugroups.exe SetVmGroup /VmName:LOG-HK-01  /GroupId:...003

# 5)检查
cpugroups.exe GetVmGroup

重要:当你往某个组里继续加 VM 时,同组 VM 会共享组 Cap。所以业务扩容或缩容时,记得重算 Cap(或者给关键 VM 配合 Set-VMProcessor -Maximum/-Reserve/-RelativeWeight 做 per‑VM 的“资源上/下限”)。

6. 我在现场的三步校准法

做完上面“结构性”的配置,我会用 3 组工具校准:

6.1 宿主:NUMA 层面观测

# 看每个宿主 NUMA 节点的内存/负载
Get-VMHostNumaNode | ft NodeIndex,MemoryAvailable,MemoryTotal,LogicalProcessorCount

# 看各节点上跑着哪些 VM(需 Windows Server 2019+ 的 cmdlet;2016 可用 cpugroups 和 PerfMon 侧证)
Get-VMHostNumaNodeStatus

6.2 宿主:Hyper‑V 处理器调度

我会开一组 PerfMon 计数器并抓 10~15 分钟:

Hyper-V Hypervisor Logical Processor(*)\% Total Run Time

Hyper-V Hypervisor Root Virtual Processor(*)\% Guest Run Time

Hyper-V Hypervisor Virtual Processor(*)\% Guest Run Time

NUMA Node Memory(*)\Local Node Accesses/sec 与 Remote Node Accesses/sec

看点很直接:Remote/Local 比例是否明显下降、关键组的 LP 是否“平稳饱和而不锯齿”。

6.3 来宾:vNUMA 感知

在大 VM(如 DB‑HK‑01)里执行:

coreinfo -n -s(Sysinternals)确认 vNUMA 节点数与每节点核心/内存是否与我们期望的拓扑一致;

SQL Server/Java 等应用层也要绑定合适的 NUMA 节点(例如 SQL 的 ALTER SERVER CONFIGURATION SET PROCESS AFFINITY NUMANODE = (...))。

7. 策略落地:一张“分配计划表”

业务 VM vCPU vRAM NUMA CPU Group Cap 备注
DB DB-HK-01 16 96GB 节点 1 ...001 100% NVMe 直通
Web WEB-HK-01 4 8GB 节点 0 ...002 70% 横向扩展
Web WEB-HK-02 4 8GB 节点 0 ...002 70% 静态内容
Log LOG-HK-01 8 32GB 节点 1 ...003 90% ELK 节点

8. 关键 PowerShell 片段(可直接用/改)

8.1 一键为一批 VM 开 vNUMA(关动态内存)

$targets = @("DB-HK-01","LOG-HK-01")
foreach ($vm in $targets) {
  Set-VMMemory -VMName $vm -DynamicMemoryEnabled $false -StartupBytes 32GB
  # 下面两行按你的宿主单节点核心数修
  Set-VMProcessor -VMName $vm -Count 16 -MaximumCountPerNumaNode 8 -MaximumCountPerNumaSocket 8
}

8.2 给 Web 类 VM 调轻量的 CPU 资源控制

$webs = @("WEB-HK-01","WEB-HK-02")
foreach ($vm in $webs) {
  Set-VMProcessor -VMName $vm -Maximum 80 -Reserve 0 -RelativeWeight 100
}

8.3 快速对齐宿主电源与计划

powercfg /SETACTIVE SCHEME_MIN
# 关闭磁盘省电等(依策略)

9. 真实坑与当天我是怎么解的

关掉 NUMA Spanning,DB VM 起不来

症状:DB‑HK‑01 报内存不足无法启动,但宿主总内存是够的。

定位:Get-VMHostNumaNode 看到节点 1 可用内存 88GB < 96GB 启动需求;关 Spanning 后,Hyper‑V 不会跨节点分配。

解决:夜间维护窗口把 LOG‑HK‑01 临时迁到节点 0(冷迁移),释放节点 1;随后 DB 成功启动。后续把 DB 启动内存调成 88GB + Buffer(例如 92GB),避免临界失败。

vNUMA 开了但来宾里看不到多节点

症状:coreinfo 显示单节点;SQL 只识别一个 numa‑node。

定位:忘了关 动态内存,这是典型错误;Dynamic Memory 会让 vNUMA 被隐藏。

解决:对该 VM Set-VMMemory -DynamicMemoryEnabled $false,重启 VM;随后看到两个 vNUMA 节点。

CPU Groups 生效了,但 Web 突发流量被“压”住

症状:压测时 P99 响应时间偏高,宿主上另一个组也很闲。

定位:我们给 Group‑WEB 设置了 70% 的 Cap 且绑了半个 NUMA,突发时 VM 想占更多 CPU 但被组上限卡住。

解决:把 ...002 的 Cap 提到 85%,同时给 WEB‑HK‑01/02 加一台 WEB‑HK‑03 做水平扩展,波峰稳定下来了。

远端内存访问率仍偏高

症状:Remote Node Accesses/sec 长期 > Local 的 10%。

定位:ELK 的 JVM -Xms/-Xmx 不一致,频繁 GC 争抢宿主内存,偶尔触发跨节点分配。

解决:把 LOG‑HK‑01 JVM 固定内存,配合静态内存 + vNUMA;远端访问占比降到 2% 左右。

10. 基准对比:把“感觉快”变成数据

上线前后,我做了一个简单对比(每项 15 分钟取中位数):

指标 调优前 调优后 变化
DB TPS 118,500 134,200 +13.2%
Remote/Local 比 0.18 0.03 −5×
Web P99 延迟 142 ms 96 ms −32%
LP 波动 0.37 0.21 −43%

这些增益不是“玄学”。DB 类工作负载对内存延迟极其敏感,把它稳稳放在一个物理 NUMA 上,收益立竿见影;Web/日志用 CPU Groups 做上限与隔离,减少相互干扰,是“稳”的来源。

11. 运维侧 Checklist(上线前/后各做一遍)

BIOS

  •  Node Interleaving = Disabled
  •  HT = Enabled
  •  SR-IOV = Enabled
  •  Power = OS/Performance
  •  固件为稳定版

宿主

  •  Windows 更新
  •  电源计划高性能
  •  NUMA 节点数与预期一致
  •  Cluster 健康

Hyper-V

  •  NUMA Spanning 策略合理
  •  大 VM 静态内存 + vNUMA
  •  vCPU 拓扑匹配

CPU Groups

  •  Affinity 正确
  •  Cap 设置合理
  •  VM 绑定正确

性能观测

  •  Remote/Local < 5%
  •  PerfMon 曲线平滑
  •  DB/WEB 压测无性能回退

来宾

  •  coreinfo 输出正确
  •  DB NUMA 配置生效
  •  JVM 内存与 NUMA 对齐

运维

  •  CMDB 更新
  •  巡检脚本有效
  •  定期备份演练

12. 常见问答(也是我现场被问最多的)

Q:我只有一颗 CPU,还有必要折腾 NUMA 吗?

A:单路物理 CPU 通常就是 1 个 NUMA 节点,vNUMA 也就没有映射意义;但 CPU Groups 依然有用,可以做资源隔离与上限控制。

Q:NUMA Spanning 一定要关吗?

A:不是“必须”。它是弹性与性能的权衡。批处理型/敏感型工作负载稳定后关掉更合适;资源紧张期或频繁弹性伸缩,可以临时开着保证可启动性。

Q:能不能把 VM 精准钉到某几个核?

A:Hyper‑V 官方路径是 CPU Groups(亲和 LP 列表 + Cap),而不是 GUI 里的“两三个勾”。我的建议:按 NUMA 和业务逻辑分组,别过度精确到单核,维护成本会爆炸。

13. 把“结构”先搭好,细节自然有位置

在香港做托管/租用,资源成本与网络时延都逼着我们“少走弯路”。NUMA 与 CPU 亲和看起来是“底层细节”,但只要把BIOS→宿主→Hyper‑V→来宾这条链一环一环抠清楚,你会发现:调优从不玄学,都是结构化的工程活。

如果你的配置与我这里不完全一样(比如 AMD EPYC、更多 NUMA 节点、或需要跨主机的 Live Migration),方法也通用:先摸清拓扑,再明确边界(Spanning/vNUMA/Cap),最后用数据闭环。做完这几步,虚拟化平台的“整体性能”就不再是口号,而是看得见、量得到、稳得住的生产力。

目录结构
全文