如何在香港服务器的 Windows Server 中配置 Receive Side Scaling(RSS),把多核 CPU 的网络性能“全部叫醒”?

凌晨 2 点,我蹲在香港将军澳机房的42U机柜前,盯着一台新上架的 Windows Server 2022 物理服务器发呆:10GbE 光口明明协商正常,业务却怎么也跑不上 5Gbps。任务管理器里一条 CPU 核心 90%+,其他核几乎闲着,我当场就笑了——典型的 RSS 没配好。
这一晚,我把从排障到调优的过程都记了下来:不是“玄学”,是一步步可复现的实操。下面我用第一人称把它完整讲清楚——无论你是第一次摸 Windows 的网络栈,还是常年在 IDC 混的老兵,都能直接照做。
现场环境与原始症状
硬件 / 系统
| 项目 | 配置 |
|---|---|
| 机房 | 香港 TKO(双上联) |
| 服务器 | Dell R650 |
| CPU | Intel Xeon Silver 4314(16C/32T,单路) |
| 内存 | 128GB DDR4 |
| 网卡 | Intel X710-DA2(10GbE SFP+ ×2,驱动 1.13.x) |
| 操作系统 | Windows Server 2022 Datacenter(21H2,带桌面体验) |
| 角色 | 物理机直连(无 vSwitch),跑 HTTP/2 服务与 gRPC(长连接多路复用) |
问题特征
- 单 TCP 流吞吐最高 ~2.8Gbps,多流总吞吐 ~4.2Gbps;
- 任务管理器显示 0 号逻辑核接近满载,其它核心低负载;
- perfmon 中 \Processor(0)% Processor Time 飙高,\Network Interface(X710)\Bytes Total/sec 明显低于链路上限。
结论几乎呼之欲出:网卡收包中断和协议栈处理基本“糊”在了单个核心上。在 Windows 上,这通常是 Receive Side Scaling(RSS) 没启、没配,或没把队列与 CPU 正确绑定。
原理一杯咖啡讲完:RSS 在 Windows 里怎么工作?
目的:把入方向的收包处理(中断、DPC、协议栈)按“流”(5 元组哈希)分散到多个 CPU 核,避免单核瓶颈。
关键部件
- RSS 队列:NIC 上的多个接收队列(RX queues),每队列可独立触发中断。
- RSS 哈希 & 重定向表(Indirection Table):按流哈希把流映射到队列/CPU。
- CPU 亲和:为每个队列设置目标 CPU(BaseProcessorNumber 起始核,MaxProcessors 使用的核数)。
- NUMA 亲和:单路 CPU 也有 L3 共享/本地性;多路或 >64 逻辑核时,还涉及 Processor Group 与 NumaNode,需要显式设定,避免跨节点/跨 Group 访问造成额外延迟。
一句话:队列要多、核要分、亲和要对。
动手前的“3 分钟预检”
# 1) 看网卡与状态
Get-NetAdapter | ? Status -eq 'Up' | ft Name, InterfaceDescription, LinkSpeed
# 2) 看 RSS 全局与 NIC 粒度状态
netsh int tcp show global | findstr /i "Receive-Side Scaling"
Get-NetAdapterRss -Name "Ethernet 2" # 把名字换成你的 X710 口
# 3) 看 CPU/NUMA 拓扑
Get-CimInstance Win32_Processor | select Name,NumberOfCores,NumberOfLogicalProcessors
Get-NetAdapterRss -Name "Ethernet 2" | fl * # 观察 NumaNode, BaseProcessorNumber, MaxProcessors
期望看到:
- Receive-Side Scaling State : enabled;
- X710 口 Enabled : True;
- NumberOfReceiveQueues ≥ 4;
- MaxProcessors 至少 4~8;
- NumaNode 合理(单路通常是 0)。
如果全都不合格,下面开始“对症下药”。
配置步骤:把队列、核和亲和一口气配到位
1)开启或确认 RSS 已启
# 全局(一般默认启用)
netsh int tcp set global rss=enabled
# 针对网卡口(很关键)
Enable-NetAdapterRss -Name "Ethernet 2"
Get-NetAdapterRss -Name "Ethernet 2"
若你看到 Enabled : False,多数是驱动/高级属性里被关了,或该口被绑定到奇怪的“配置文件”。
2)把接收队列数量拉满(取决于 NIC 能力)
不同驱动的“高级属性”显示名不一致,先把它们列出来:
Get-NetAdapterAdvancedProperty -Name "Ethernet 2" |
sort DisplayName | ft DisplayName, DisplayValue -AutoSize
常见关键词:“RSS Queues / Receive Side Scaling Queues / #RSS Queues”。把它设到一个合理的值(X710 通常 4/8 没问题):
# 示例:设置为 8 个接收队列
Set-NetAdapterAdvancedProperty -Name "Ethernet 2" `
-DisplayName "RSS Queues" -DisplayValue "8 Queues"
有些驱动用“Receive Buffers(接收环形缓冲)”限制并发度,建议一并调大到 2048~4096。
Set-NetAdapterAdvancedProperty -Name "Ethernet 2" `
-DisplayName "Receive Buffers" -DisplayValue "4096"
3)把 RSS 映射到一段连续 CPU 核(避开 0 号核)
经验上我会避开 0 号核(系统/中断容易“粘”在它上面),从 2 或 4 开始:
# 用 8 个逻辑核,从第 2 号逻辑核开始
Set-NetAdapterRss -Name "Ethernet 2" `
-BaseProcessorNumber 2 -MaxProcessors 8
Get-NetAdapterRss -Name "Ethernet 2" | fl BaseProcessorNumber,MaxProcessors,MaxProcessorNumber
多于 64 逻辑核的机器请额外指定 Processor Group,否则只会用到 Group 0 的核:
Set-NetAdapterRss -Name "Ethernet 2" `
-BaseProcessorGroup 0 -MaxProcessorGroup 0 `
-BaseProcessorNumber 2 -MaxProcessors 16
NUMA 注意:若是双路/多 NUMA,优先把 RSS 绑定到与该 NIC 直连的 NUMA 节点(通常是 0 或 1):
Set-NetAdapterRss -Name "Ethernet 2" -NumaNode 0
4)RSC/LSO 等卸载项:一般保持开启
RSC(Receive Segment Coalescing):减少每包处理开销,对长连接吞吐更友好;
LSO/Checksum Offload:对发送路径/校验和卸载有效,一般保持开启。
检查/调整:
Get-NetAdapterAdvancedProperty -Name "Ethernet 2" | ? DisplayName -match "RSC|LSO|Checksum"
# 如需改:
# Set-NetAdapterAdvancedProperty -Name "Ethernet 2" -DisplayName "IPv4 Checksum Offload" -DisplayValue "Enabled"
5)中断调制(Interrupt Moderation)
过高会加大延迟、降低包率;过低会让 CPU 被中断“打爆”。X710 的“Interrupt Moderation Rate”我一般设为 Adaptive 或 Medium,并结合实测再微调:
Set-NetAdapterAdvancedProperty -Name "Ethernet 2" `
-DisplayName "Interrupt Moderation Rate" -DisplayValue "Adaptive"
6)电源与 BIOS
Windows 电源计划改为 高性能:
powercfg /S SCHEME_MIN
BIOS 里尽量关闭深度 C-States(视业务而定),确保 PCIe ASPM 不影响中断延迟。
压测:用 ntttcp/iperf3 看“术后对比”
工具选择
微软出品 NTTTCP 更贴近 Windows 网络栈特性,多流/亲和好用;
也可以用 iperf3 辅助观测多流行为。
准备:对端找一台同样 10GbE 的测试机(同机房或跨机房都可以),时间同步,确保链路与 MTU(1500/9000)一致。
NTTTCP 示例
被测服务器(接收端):
# 16 条接收流,持续 60s
.\ntttcp.exe -r -m 16,*,192.168.10.10 -t 60
发送端:
# 16 条发送流,窗口合适即可
.\ntttcp.exe -s -m 16,*,192.168.10.10 -t 60
iPerf3 示例
- 接收端:iperf3 -s
- 发送端:iperf3 -c 192.168.10.10 -P 16 -t 60
我现场的对比数据
| 指标 | 调优前 | 调优后 |
|---|---|---|
| 多流总吞吐(NTTTCP) | 4.2 Gbps | 9.6 Gbps |
| 单流(iPerf3 -P 1) | 2.8 Gbps | 5.3 Gbps |
| 逻辑核最高占用 | 92%(Core#0) | 55%(Core#2~9 平均分摊) |
| DPC 队列长度峰值 | 2.8 | 0.7 |
| 丢包(交换机口计) | 偶发 | 趋近 0 |
验证脚本(采样 CPU 与网络计数器):
# 采样 60s 的关键计数器
$ctrs = @(
'\Processor(_Total)\% Processor Time',
'\Processor(2)\% Processor Time','\Processor(3)\% Processor Time','\Processor(4)\% Processor Time',
'\Network Interface(*)\Bytes Total/sec',
'\TCPv4\Segments/sec'
)
Get-Counter -Counter $ctrs -SampleInterval 1 -MaxSamples 60 |
Select -ExpandProperty CounterSamples |
Select TimeStamp,Path,CookedValue |
Format-Table -AutoSize
常见坑与我的“现场解法”
RSS 全局启了,但 NIC 口还是不分核
现象:netsh int tcp show global 是 enabled,Get-NetAdapterRss 却显示 Enabled : False。
解法:Enable-NetAdapterRss -Name <NIC>;若仍失败,更新驱动到厂商版本(Windows 盒装驱动有时阉割高级属性)。
队列设了很多,吞吐反而不稳
现象:抖动、丢包增多、CPU 抖动。
解法:适当减少队列(例如 8→4)、把 Interrupt Moderation 设为 Adaptive,并把 Receive Buffers 拉大到 4096。
>64 逻辑核的机器只吃 Group 0
现象:单 Group 满载,其他 Group 闲着。
解法:显式设置 -BaseProcessorGroup/-MaxProcessorGroup;必要时按 NUMA 分组绑定不同 NIC 口,做到“一口一组”。
Hyper-V 场景 VMQ/vRSS 冲突认知
要点:物理 NIC 上建议 开启 VMQ;VM 内网卡上 启用 vRSS,才能把队列从外层分到来宾多核。
# 宿主
Enable-NetAdapterVmq -Name "vEthernet (External)"
# 来宾
Set-VMNetworkAdapter -VMName "AppVM-01" -VrssEnabled $true -VmqWeight 100
若你不是虚拟化场景,不要纠结 VMQ,专注 RSS 即可。
Windows 更新后驱动把高级属性“洗掉”
现象:吞吐忽降,查看发现队列/亲和回到了默认。
解法:把配置写成开机脚本固化,或者在任务计划里“网络上线后”运行一遍(下面给出脚本)。
MTU 与对端不一致
现象:大包分片、吞吐起不来。
解法:统一 MTU(1500 或 9000),交换机/对端/主机三端一致。
netsh interface ipv4 set subinterface "Ethernet 2" mtu=1500 store=persistent
一键固化:我在现场留下的 PowerShell 脚本
下面这个脚本我放在 C:\ops\rss-tune.ps1,配合任务计划(系统启动后 1 分钟运行):
$nic = "Ethernet 2" # 换成你的口名
$base = 2 # 从第 2 号逻辑核开始
$procs = 8 # 使用 8 个逻辑核
$rxQueues = "8 Queues"
$rxBuf = "4096"
$imr = "Adaptive"
function Wait-AdapterReady($name){
for($i=0;$i -lt 30;$i++){
$a = Get-NetAdapter -Name $name -ErrorAction SilentlyContinue
if($a -and $a.Status -eq "Up"){ return }
Start-Sleep -Seconds 2
}
throw "Adapter $name not ready"
}
Wait-AdapterReady $nic
Enable-NetAdapterRss -Name $nic -ErrorAction SilentlyContinue
Set-NetAdapterRss -Name $nic -BaseProcessorNumber $base -MaxProcessors $procs
# 高级属性:队列、缓冲、中断调制(不同驱动显示名可能不同,失败会被忽略)
$props = Get-NetAdapterAdvancedProperty -Name $nic
if($props | ? DisplayName -match 'RSS Queues'){
Set-NetAdapterAdvancedProperty -Name $nic -DisplayName "RSS Queues" -DisplayValue $rxQueues -ErrorAction SilentlyContinue
}
if($props | ? DisplayName -match 'Receive Buffers'){
Set-NetAdapterAdvancedProperty -Name $nic -DisplayName "Receive Buffers" -DisplayValue $rxBuf -ErrorAction SilentlyContinue
}
if($props | ? DisplayName -match 'Interrupt Moderation Rate'){
Set-NetAdapterAdvancedProperty -Name $nic -DisplayName "Interrupt Moderation Rate" -DisplayValue $imr -ErrorAction SilentlyContinue
}
# 打印结果
Get-NetAdapterRss -Name $nic | fl Name,Enabled,BaseProcessorNumber,MaxProcessors,MaxProcessorNumber,NumberOfReceiveQueues,NumaNode
验收清单(上线前最后 5 分钟)
- Get-NetAdapterRss 显示 Enabled=True、MaxProcessors 合理;
- 高级属性里 RSS Queues=预期值、Receive Buffers≥2048;
- 任务计划中固化脚本已添加,系统重启后配置仍在;
- ntttcp/iperf3 多流吞吐 ≥ 9Gbps(同机房 10GbE)或达到跨机房预期;
- perfmon 中各核心负载均衡,DPC 长度无异常峰值;
- 交换机口无递增丢包,FEC/错误包计数稳定。
从“一个核干到秃”到“八仙过海,各显神通”
那晚我在机房把最后一条命令敲下去,又跑了一把 ntttcp,看着吞吐从 4Gbps 一路顶到 9.6Gbps,任务管理器里几条 CPU 核均匀地跳动,心里那口气也顺了。
RSS 不是神秘开关,它很“实”,就藏在网卡驱动与 Windows 的几个参数里。把队列数拉起来,把核的亲和绑正确,把缓冲与中断调平衡,你就能把多核的潜力稳稳接住。
下回你在香港的机房里遇到“单核拉满、吞吐上不去”的怪象,想想今天这套流程。也许你会像我一样,听着空调的风声,笑着把那条顽固的 0 号核“从一线调回幕后”,让整台服务器真正跑起来。