我在香港机房为电竞平台“止掉线”的那一夜:Windows Server 2019 Socket 与内核参数优化全记录

晚上 7 点半,香港机房值班电话响了。华南高峰期刚起,平台观战房与匹配服掉线报警从 0.6% 蹿到 2.8%,客服群里玩家在刷“频繁重连”。我盯着 NOC 大屏:丢包抖动主要出现在回源(CN↔HK)段,但服务端重传与半开连接也异乎寻常。
我决定先稳住:开只读扩容、分流静态资源,随后直接进机房对几台关键的 Windows Server 2019 做“低时延取向”的内核与 Socket 优化。
环境与基线(上手前先摸清家底)
硬件/系统与网卡
- 机型:Supermicro 1U(类似 SYS-1029 系列),
- CPU:Intel Xeon Silver 4214R ×2(24C/48T,总主频 2.4GHz,睿频 3.5GHz)
- 内存:128 GB DDR4-2666
- 系统盘:双盘 NVMe(RAID1)
- 网卡:Intel X710-DA2(10GbE,单链路上行)
- OS:Windows Server 2019 Datacenter(1809 系列内核,打到近半年补丁)
- 业务:.NET Core 网关 + 长连接会话层(WebSocket/TCP 443),匹配服务(TCP 8443),房间心跳(UDP 20000-20100,少量)
基线指标(变更前 10 分钟滚动平均)
| 指标 | 数值 |
|---|---|
| 掉线率(玩家侧 1 分钟内需重连的会话占比) | 2.8% |
| TCP 重传率(服务端视角) | 2.1% |
| 95% RTT(CN↔HK) | ~320 ms |
| SYN 超时比(3 次握手未成比例) | 0.9% |
| TIME_WAIT 总量 | > 120k |
| WebSocket 断连后 3 秒内重连成功率 | 88% |
快照检查(保留状态,支持回滚)
# 全局 TCP 设置
netsh int tcp show global
# TCP 模板(Windows 2019 有“Internet”等模板)
Get-NetTCPSetting | ft SettingName,AutoTuningLevelLocal,ECNCapability,CongestionProvider,Timestamps
# 网卡队列与卸载能力
Get-NetAdapter | ft Name,Status,LinkSpeed
Get-NetAdapterRss | ft Name,Enabled,BaseProcessorNumber,MaxProcessors,Profile
Get-NetAdapterAdvancedProperty -Name "Ethernet 10G" | ft DisplayName,DisplayValue
变更策略与风险控制
- “先测后改,小步快跑”:按模块推进,每一步仅改 2~3 项参数,并记录指标。
- “能回滚就回滚”:每组命令都有反向操作;关键注册表变更先导出。
- “针对链路特性”:CN↔HK 长肥管道 + 偶发拥塞,目标是提升拥塞信号敏感度、加快死链检测、减少 TIME_WAIT 压力,同时把 CPU/网卡中断压在“可控低延迟区间”。
Step 1:电源与调度,把“系统级抖动”按住
切到高性能电源计划(减少 C-State 进出延迟)
# 开启“终极性能”并切换(Server 2019 可用)
powercfg -duplicatescheme e9a42b02-d5df-448d-aa00-03f14749eb61
powercfg /S e9a42b02-d5df-448d-aa00-03f14749eb61
# 可选:把最小处理器状态拉到 100%
powercfg -setacvalueindex scheme_current sub_processor PROCTHROTTLEMIN 100
powercfg -setactive scheme_current
效果:CPU 睿频/降频抖动显著减少,线程调度稳定。
Step 2:网卡与队列(RSS/RSC/LSO),决定“每个包落哪颗核”
目标:让高并发短报文分摊到多个核心,避免单核中断打满;同时按“低时延优先”策略谨慎使用合并/分段卸载。
开启 RSS,固定亲和,贴近 NUMA
# 开启 RSS 并设置亲和
Enable-NetAdapterRss -Name "Ethernet 10G"
Set-NetAdapterRss -Name "Ethernet 10G" -BaseProcessorNumber 2 -MaxProcessors 8 -Profile Closest
RSC/LSO 策略
- RSC(接收段合并):吞吐友好,但可能增加尾延迟。我们的实时业务以低延迟为先,禁用。
- LSO(大包分段卸载):CPU 省事,但某些驱动版本在高 PPS 下引入 jitter。我们 保留 LSO,若尾延迟不降再禁用对比。
# 禁用 RSC
Set-NetAdapterRsc -Name "Ethernet 10G" -IPv4 Disabled -IPv6 Disabled
# 保留 LSO(若要禁用以对比)
# Disable-NetAdapterLso -Name "Ethernet 10G"
小结(阶段性观测)
- 单核中断峰值下降约 35%,队列更均衡。
- 95% RTT 有轻微改善(~300 ms)。
Step 3:TCP 栈“低时延取向”——拥塞控制、ECN、时间戳、窗口自调
目标:对拥塞更敏感(早知道早收敛),提升可观测性(时间戳),保证跨境链路的收发窗口自适应。
选择拥塞算法 + 打开 ECN + 开启时间戳 + 保持自适应窗口
# 以“Internet”模板为基础,直接调模板
Set-NetTCPSetting -SettingName Internet `
-CongestionProvider CUBIC `
-ECNCapability Enabled `
-Timestamps Enabled `
-AutoTuningLevelLocal Normal
说明:
- CUBIC:对高 BDP 链路更友好,收敛/恢复速度适配跨境场景。
- ECN:让队列溢出前通过标记反馈拥塞,减少硬丢包与重传。
- Timestamps:支持 PAWS/RTT 估计,更稳定。
- AutoTuning Normal:大多数中间盒可兼容;若遇到“窗口黑洞”,降到 Restricted。
验证
netsh int tcp show global
Get-NetTCPSetting -SettingName Internet | fl
Step 4:端口与 TIME_WAIT 压力管理(大规模短连接场景关键)
扩大动态端口区间(避免端口耗尽)
# TCP/UDP 动态端口范围
netsh int ipv4 set dynamicport tcp start=10000 num=55535
netsh int ipv4 set dynamicport udp start=10000 num=55535
降低 TIME_WAIT 保留时间(默认 120s → 30s)
谨慎:降太低会增加端口复用碰撞风险;30s 在我们业务里验证可行。
# 先备份注册表
reg export "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" C:\reg_tcpip_bak.reg
# 设置 TcpTimedWaitDelay = 30(单位:秒)
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" `
-Name "TcpTimedWaitDelay" -PropertyType DWord -Value 30 -Force
阶段效果
- TIME_WAIT 数量峰值从 120k 降至 ~45k。
- 某些反向代理进程端口回收更顺畅,SYN 拒绝明显下降。
Step 5:Keep-Alive 与死链探测(WebSocket 业务必须快准狠)
Windows 默认 Keep-Alive 2 小时太慢,长链业务要主动探测,服务端与客户端一致调快。
全局 Keep-Alive(系统级兜底)
# KeepAliveTime = 15000 ms(15s),KeepAliveInterval = 1000 ms(1s)
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" `
-Name "KeepAliveTime" -PropertyType DWord -Value 15000 -Force
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" `
-Name "KeepAliveInterval" -PropertyType DWord -Value 1000 -Force
应用层 Socket 选项(.NET Core 侧)
// 关键摘录:为长连接/实时推送的 Socket 明确低延迟取向
var socket = tcpClient.Client;
socket.NoDelay = true; // TCP_NODELAY,禁用 Nagle
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
// Windows 下用 IOControl 精细化 KA 定时
// 结构:onoff, keepalivetime(ms), keepaliveinterval(ms)
var ka = new byte[12];
BitConverter.GetBytes(1).CopyTo(ka, 0); // on
BitConverter.GetBytes(15_000).CopyTo(ka, 4); // 15s
BitConverter.GetBytes(1_000).CopyTo(ka, 8); // 1s
socket.IOControl(IOControlCode.KeepAliveValues, ka, null);
// 连接关闭策略:避免长时间 FIN_WAIT
socket.LingerState = new LingerOption(false, 0); // 立即 RST 关闭(按需使用)
注:RST 关闭会让对端立刻感知断链,减少半闭等待;但会丢未发送完的数据,请确保上层有幂等/重试。
Step 6:SYN/重传参数(更稳握手,更快失败)
# 降低 SYN 重试次数(默认 6),我们设为 4,避免“假死等太久”
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" `
-Name "TcpMaxSynRetransmissions" -PropertyType DWord -Value 4 -Force
# 数据重传上限(默认 5),保持 5 或 6,视链路而定
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" `
-Name "TcpMaxDataRetransmissions" -PropertyType DWord -Value 5 -Force
Step 7:MTU/PMTUD 与分片(跨境“黑洞”要小心)
默认 1500 MTU。跨境链路若遇到隧道/PPPoE,可能有效 MTU < 1500。
策略:保持 PMTUD 开启,若观测到 ICMP 受限导致“黑洞”,再在边界设备做 MSS Clamping。
# 显示并确保 pmtudiscovery 启用(一般默认启用)
netsh interface ipv4 show global
# 需要时固定某接口 MTU(示例)
# netsh interface ipv4 set subinterface "Ethernet 10G" mtu=1500 store=persistent
Step 8:内核诊断与压测回路(别拍脑袋,靠证据)
快速采样
# 网络连接失败与重传
Get-Counter "\TCPv4\Connection Failures","\\TCPv4\Segments Retransmitted/sec" -SampleInterval 5 -MaxSamples 12
# PktMon 抓包(Server 2019 可用)
pktmon filter add -d ipv4
pktmon start --etw -p 0
# 结束并导出
pktmon stop
pktmon etl2pcapng PktMon.etl -o PktMon.pcapng
端到端连通性/端口耗尽检测
Test-NetConnection -ComputerName gw.example.hk -Port 443
netstat -ano | findstr /c:"TIME_WAIT" | measure
吞吐/延迟对照(内网链路)
生产不建议长时间跑;用维护窗口做样本。
iperf3(Windows 版)可用来确认 RSS/RSC/LSO 对吞吐与尾延迟的影响。
阶段性结果(改完一轮后的 20 分钟)
| 指标 | 变更前 | 变更后(20 分钟) |
|---|---|---|
| 掉线率 | 2.8% | 0.9% |
| TCP 重传率 | 2.1% | 0.8% |
| 95% RTT | ~320 ms | ~210 ms |
| SYN 超时比 | 0.9% | 0.3% |
| TIME_WAIT 总量 | >120k | ~45k |
| 3 秒内重连成功率 | 88% | 97% |
我让值班同事继续观察 1 小时,再决定是否做第二轮(例如禁用 LSO 进一步榨延迟、或把 AutoTuning 调到 Restricted 以防某些链路黑洞)。
Step 9:策略二选一的对照试验(按需启/停)
A. 进一步压尾延迟(若观战仍卡顿)
# 禁用 LSO 试验
Disable-NetAdapterLso -Name "Ethernet 10G"
# 将 AutoTuning 调低,兼容老设备
Set-NetTCPSetting -SettingName Internet -AutoTuningLevelLocal Restricted
预期:尾延迟再降 5~10%,但 CPU 开销可能上浮 8~15%。
B. 回到吞吐优先(赛事回放/大房间推流)
# 保持 LSO,按需恢复 RSC
Set-NetAdapterRsc -Name "Ethernet 10G" -IPv4 Enabled -IPv6 Enabled
Set-NetTCPSetting -SettingName Internet -AutoTuningLevelLocal Normal
现场“坑点”与解法
X710 某版本驱动 + RSC 在高 PPS 下引入抖动
现象:p99 延迟忽高忽低,CPU 中断没上天但丢包上升。
解:禁用 RSC 后恢复稳定;后续在维护窗口升级驱动再复测。
TIME_WAIT 过多导致端口耗尽
现象:短时连接打满,SYN 被拒。
解:扩大动态端口区间 + 将 TcpTimedWaitDelay 降到 30s;对反向代理启用连接池。
某对端链路疑似 ICMP 限制导致 PMTUD 黑洞
现象:长时间握手卡住、重传率升高。
解:边界网关做 MSS clamping(TCP 端口 443/8443),应用层维持 TCP_NODELAY 与合理 SendBuffer。
应用层忘记开 Keep-Alive
现象:NAT/防火墙空闲回收,玩家感知“随机掉”。
解:系统全局 KA + 业务 Socket IOControl 精细化;双端一致。
安全软件实时扫描日志目录
现象:I/O 抖动影响处理线程,间接拉高延迟。
解:给日志目录加排除规则,I/O 热区与数据盘分离。
回滚与变更单(出问题 1 分钟内撤)
电源计划:powercfg /S SCHEME_BALANCED
RSS/RSC:
Disable-NetAdapterRss -Name "Ethernet 10G"
Set-NetAdapterRsc -Name "Ethernet 10G" -IPv4 Enabled -IPv6 Enabled
TCP 模板:
Set-NetTCPSetting -SettingName Internet -CongestionProvider Default `
-ECNCapability Disabled -Timestamps Disabled -AutoTuningLevelLocal Normal
注册表:
reg import C:\reg_tcpip_bak.reg
动态端口/MTU:参照原值恢复。
最终成效(观察 24 小时的日报)
指标 高峰平均 非高峰平均
掉线率 0.42% 0.18%
TCP 重传率 0.6% 0.3%
p95 RTT ~180 ms ~140 ms
3 秒重连成功率 98.6% 99.1%
玩家舆情不再“刷屏”,赛事直播顺滑了。运维群里只剩零星的问题单。
附:我保留下来的“标准化操作清单”(SOP)
- 变更前:netsh int tcp show global、Get-NetTCPSetting、Get-NetAdapter* 拍快照;reg export 备份。
- 电源:切“终极性能”,锁最小处理器状态 100%。
- 网卡:启用 RSS(设置 Base/Max/NUMA),禁 RSC,LSO 先保留。
- TCP 模板:CUBIC + ECN + Timestamps + AutoTuning Normal。
- 端口/TIME_WAIT:扩大动态端口区间,TcpTimedWaitDelay=30。
- Keep-Alive:系统 15s/1s,业务 Socket 开 TCP_NODELAY 与 KA。
- SYN/重传:TcpMaxSynRetransmissions=4,TcpMaxDataRetransmissions=5。
- PMTUD/MTU:保持启用;必要时网关做 MSS Clamping。
- 验证:PktMon/PerfMon 指标对照;必要时 LSO on/off、AutoTuning Normal/Restricted 做 A/B。
- 回滚:一键导入注册表备份 + 恢复模板与网卡卸载能力。
凌晨两点,我端着机房自助咖啡,沿着冷风呼呼的走廊回到 NOC。大屏上那条掉线率曲线终于平稳贴着地面。
这些参数看似琐碎,背后是你对链路与业务形态的理解:跨境长肥管道、实时交互的尾延迟、NAT 与会话的寿命、以及内核如何响应拥塞信号。
做运维的浪漫,可能就是在最糟的时刻,用一组稳妥的系统级改动,让玩家“无感知地继续赢下那一局”。
参考取值与对照(便于拷贝到变更单)
| 类别 | 项 | 推荐值 | 说明 |
|---|---|---|---|
| 电源 | 计划 | 终极性能 | 降低频率抖动 |
| 网卡 | RSS | 开启,Base=2,Max=8 | 视核心数/NUMA 调 |
| 网卡 | RSC | 禁用 | 优先低延迟 |
| 网卡 | LSO | 开启(必要时禁用对比) | 观察 CPU/尾延迟 |
| TCP | 拥塞算法 | CUBIC | 高 BDP 友好 |
| TCP | ECN | Enabled | 早期拥塞信号 |
| TCP | Timestamps | Enabled | 稳定 RTT 估计 |
| TCP | AutoTuning | Normal(遇黑洞降 Restricted) | 窗口自适应 |
| 端口 | 动态端口 | TCP/UDP 10000–65535 | 缓解端口耗尽 |
| 连接 | TIME_WAIT | 30s | 降低句柄压力 |
| Keep-Alive | 系统 | 15s / 1s | 快速探测死链 |
| SYN | 重试 | 4 | 更快失败 |
| 重传 | 数据 | 5 | 默认即可 |
| MTU | PMTUD | 开启 | 必要时网关 MSS |
如果你的平台 UDP 协议占比更高(纯实时对战),在网关上加上 DSCP 标记(内部网段有效)与 队列优先级 会很香;公网不可控就别强求。