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

凌晨两点,香港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),最后用数据闭环。做完这几步,虚拟化平台的“整体性能”就不再是口号,而是看得见、量得到、稳得住的生产力。