
凌晨两点,香港MEGA-i机房内,我蹲在42U机柜底层把最后一根 DAC 线插进25G 交换机,上层两台 Windows Server 2022 正在跑验证脚本,NLB 集群不停闪黄灯。第二天上午十点要开区,匹配服务要抗住首发洪峰。我不想靠“玄学”,只认具体指标、可复现的流程和能救火的应急脚本。
目标与约束
业务目标:在香港机房部署一组匹配服务器(match server),支撑万级并发,满足房间匹配/开服高峰,对外提供 TCP/UDP 入口。
环境约束:
- 操作系统:Windows Server 2022 Datacenter(部分节点 2019 亦可)
- 负载均衡:优先软件方案(Windows NLB、Nginx-Stream on Windows),必要时预留硬件 LB/云 LB 接入位。
- 协议:HTTP/HTTPS(匹配 API)、TCP/UDP(实时对战/会话保持)。
- 网络:香港本地 25G 接入,向大陆/东南亚有多线 BGP(PCCW/CMI/NTT),后端专网 10/25G。
- 上线窗口:T+0 夜间变更,可随时回滚。
1. 架构总览(说在前面)
我们把“匹配”从“实时长连”里剥离:
- 匹配 API(HTTP/gRPC):无状态,放在 HTTP 负载均衡后,做令牌校验、分配后端房间节点。
- 房间/会话服务器(TCP/UDP):长连接与状态保持,不走七层,直连后端节点或走四层 LB。
- 服务发现:Consul(Windows 版)记录每台房间节点的健康度/负载;匹配 API 按一致性哈希或最少连接挑选节点并把“直连地址”返给客户端。
- 四层 LB只做入口聚合/灰度/应急(例如需要一个固定域名/端口给客户端),否则尽量直连减少抖动。
[Client]
| HTTPS
v
[Edge/WAF/HTTPS LB] ---> [Match API (Windows Service, Kestrel)] ---> [Consul/Redis]
| (select node)
v
[Room Server List (Windows)] <--- Health checks
|
TCP/UDP Direct or via L4 LB (Nginx-Stream/NLB)
v
[Room Server (Windows)]
2. 硬件与网络选型(我真装过的那几台)
| 角色 | 型号/形态 | CPU | 内存 | 系统盘 | 数据盘 | 网卡 | OS |
|---|---|---|---|---|---|---|---|
| Match API 节点 ×2 | Dell R650 | Xeon Gold 6430 ×1 (32c) | 128 GB | 2×480G SATA | 2×3.84TB NVMe | 2×25G SFP28 | WS 2022 |
| Room 节点 ×6 | Dell R650 / HPE DL360 | 同上或 24c 起 | 128–256 GB | 2×480G | 2×3.84TB NVMe | 2×25G | WS 2022 |
| LB 节点 ×2 | 同上 | 16–24c | 64–128 GB | 2×480G | - | 2×25G | WS 2022 |
| 存储/监控 | 机房共享或独立盒子 | - | - | - | - | 10/25G | - |
交换机:2× Top-of-Rack 25G(IRF/MLAG),前端公网 VLAN,后端管理/存储 VLAN 分离。
IP 规划(示例):
| 网络 | 用途 | 网段 | 备注 |
|---|---|---|---|
| VLAN 100 | 公网/对外入口 | 103.x.x.0/24 | LB/入口 VIP |
| VLAN 200 | 后端业务 | 10.10.20.0/24 | API ⇄ Room |
| VLAN 210 | 管理 | 10.10.21.0/24 | RDP/监控/备份 |
3. 系统级优化(上机先做不走弯路)
3.1 BIOS/电源/频率
- BIOS 里关掉深度 C-State,开高性能/性能偏好,固定全核睿频。
- NUMA 拥抱:让房间进程绑核(后文有示例)。
3.2 Windows 网络栈
# 高性能电源
powercfg -setactive SCHEME_MIN
# RSS/RSC/Offload
netsh int tcp set global rss=enabled autotuninglevel=normal
netsh int tcp set global rsc=enabled
# 某些 NIC 需要手工关/开 LSO 和中断调制,建议在驱动属性里按厂商白皮书设置
# 提升连接队列
reg add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v TcpNumConnections /t REG_DWORD /d 0x00fffffe /f
# 防火墙快速规则(示例:TCP 40000-40100,UDP 30000-30100)
New-NetFirewallRule -DisplayName "Room-TCP" -Direction Inbound -Protocol TCP -LocalPort 40000-40100 -Action Allow
New-NetFirewallRule -DisplayName "Room-UDP" -Direction Inbound -Protocol UDP -LocalPort 30000-30100 -Action Allow
3.3 NIC Teaming / VLAN
单机双口 25G:Switch Independent / Dynamic 模式(LBFO)或使用 SET(配合 Hyper-V)。
前端与后端 VLAN 分离,后端可开 MTU 9000(仅在交换机/端到端都支持时)。
4. 基础件安装(可打“金镜像”)
# .NET 8 Hosting Bundle(跑 Kestrel 的服务)
# (离线包放在 \\repo\pkgs\dotnet-hosting-8.0.x-win.exe)
Start-Process -FilePath "\\repo\pkgs\dotnet-hosting-8.0.x-win.exe" -ArgumentList "/quiet" -Wait
# IIS 可选(只当你需要)
Install-WindowsFeature Web-Server, Web-WebSockets, Web-Http-Logging
# Nginx for Windows(作为四层LB)
# 解压到 C:\nginx,使用 NSSM 注册为服务
nssm install nginx "C:\nginx\nginx.exe"
nssm set nginx AppDirectory "C:\nginx"
nssm start nginx
# Consul (Windows) - 服务发现
New-Item -ItemType Directory C:\consul
Copy-Item \\repo\pkgs\consul.exe C:\consul\
nssm install consul "C:\consul\consul.exe" agent -server -bootstrap-expect=1 -data-dir="C:\consul\data" -bind=10.10.20.10 -client=0.0.0.0
nssm start consul
5. 服务形态与发布
5.1 Match API(.NET 8 Windows 服务)
只做鉴权 + 选址:根据玩家 playerId/region/mmr 选择一个健康的房间节点,把IP:Port返给客户端。
一致性哈希示意(C# 伪码):
var nodes = consul.GetHealthy("room"); // 带当前连接数/CPU/延迟
var node = ConsistentHash.Select(playerId, nodes); // 或最小连接 + 亲和
return new { endpoint = $"{node.ip}:{node.port}", token = Sign(node, playerId) };
5.2 Room Server(Windows Service)
每台监听 TCP:40000-40100、UDP:30000-30100(按服拆分)。
启动参数包含 --affinity "0-15" 等,结合 start /affinity 或 Job Object 绑定 NUMA。
6. 负载均衡方案对比与选择
| 方案 | 层次 | 协议 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|---|
| Windows NLB | L4 | TCP/UDP | 原生、简单、共享VIP | 对交换机有要求(单播/多播),大流量抖动可能性,功能弱 | 小规模直进产线/应急 |
| Nginx-Stream on Windows | L4 | TCP/UDP | 配置灵活、灰度/限流易做 | Windows 版本生态略弱,健康检查主要被动 | 中等规模、需要灵活路由 |
| 硬件/商用 LB(F5/Kemp/NGINX Plus) | L4/L7 | TCP/UDP/HTTP | 强大的健康检查、统计、稳定 | 成本高 | 核心外网入口/大规模 |
我的做法:外网入口用商用或云 LB(可选),内网/游戏端口用 Nginx-Stream,NLB 作为兜底或简单场景。
7. 实施 A:Windows NLB(四层 VIP)
这一步是我在凌晨先通的“兜底通路”,方便立刻有 VIP 给客户端验证与联调。
7.1 安装与建群集
Install-WindowsFeature NLB -IncludeManagementTools
# 在 LB 节点上创建 NLB 群集(建议单播模式 + 独立前端 NIC)
New-NlbCluster -InterfaceName "Ethernet1" -ClusterPrimaryIP 103.x.x.10 -OperationMode Unicast
# 添加节点
Add-NlbClusterNode -NewNodeName "lb-02" -InterfaceName "Ethernet1" -HostName "lb-02" -ClusterName "103.x.x.10"
# 端口规则(TCP 40000-40100, UDP 30000-30100)
# NLB 用端口规则把 VIP 流量分散到两个 LB 节点,再由 LB 节点转发到后端(或直接在 LB 节点本机跑 Nginx-Stream)
Add-NlbClusterPortRule -ClusterName "103.x.x.10" -StartPort 40000 -EndPort 40100 -Protocol TCP -Affinity Single
Add-NlbClusterPortRule -ClusterName "103.x.x.10" -StartPort 30000 -EndPort 30100 -Protocol UDP -Affinity None
坑 1:交换机 ARP/单播模式
单播模式下,NLB 会让交换机看不到真实 MAC ↔ IP 的映射,容易泛洪。
解决:前端 NIC 独立,与后端分 VLAN;必要时让机房静态 ARP 或改 多播 + IGMP(机房得开 IGMP Snooping)。
坑 2:NLB 与 NIC Teaming 冲突
某些驱动/固件版本会导致 NLB 漂移/掉包。我的实践:前端口不做 Team,后端口可 Team。
8. 实施 B:Nginx-Stream on Windows(四层路由/灰度)
C:\nginx\conf\nginx.conf(简化版,用被动健康检查)
worker_processes auto;
events { worker_connections 65535; }
stream {
# TCP (房间)
upstream room_tcp_backend {
least_conn;
server 10.10.20.31:40000 max_fails=3 fail_timeout=10s;
server 10.10.20.32:40000 max_fails=3 fail_timeout=10s;
server 10.10.20.33:40000 max_fails=3 fail_timeout=10s;
}
server {
listen 0.0.0.0:40000;
proxy_connect_timeout 1s;
proxy_timeout 30m; # 长连
proxy_pass room_tcp_backend;
}
# UDP (房间)
upstream room_udp_backend {
hash $remote_addr consistent; # UDP 粗粒度亲和
server 10.10.20.31:30000 max_fails=3 fail_timeout=10s;
server 10.10.20.32:30000 max_fails=3 fail_timeout=10s;
server 10.10.20.33:30000 max_fails=3 fail_timeout=10s;
}
server {
listen 0.0.0.0:30000 udp;
proxy_responses 0; # 不期望响应限制
proxy_timeout 5m;
proxy_pass room_udp_backend;
}
}
要点:
TCP 用 least_conn 保持均衡;UDP 用 hash $remote_addr consistent 粗粘性(UDP 无连接,尽量稳定)。
主动健康检查若需要,考虑商用 NGINX Plus 或在 Match API 侧摘除不健康节点(动态生成 upstream 列表并 nginx -s reload)。
灰度发布(把新节点加权 10%):
upstream room_tcp_backend {
least_conn;
server 10.10.20.31:40000 weight=9;
server 10.10.20.99:40000 weight=1; # 新版
}
9. 服务发现与健康度(Consul on Windows)
注册示例 C:\consul\conf\room-31.json:
{
"service": {
"name": "room",
"id": "room-31-30000",
"address": "10.10.20.31",
"port": 30000,
"check": {
"name": "udp-port-open",
"shell": "powershell -Command \"& {Test-NetConnection -ComputerName 10.10.20.31 -Port 30000 -InformationLevel Quiet}\"",
"interval": "10s",
"timeout": "2s"
},
"meta": {
"cpu": "24",
"region": "hk",
"maxRooms": "2000"
}
}
}
注意:UDP 健康探测可用自定义探针脚本(发送特定握手包),上例仅演示端口可达。
Match API 选址策略:
- 优先同城/低 RTT;
- 过滤 check == passing;
- least_conn 或 consistent_hash(playerId);
- 返回直连地址 + 短期授权 token(避免被外部滥用直连端口)。
10. 监控与容量估算(可复现实数)
10.1 核心指标
| 类别 | 指标 | 目标/告警线 |
|---|---|---|
| 系统 | CPU<65%、RunQueue<1×核数 | 10m 中位 + P95 |
| 网络 | 单口带宽<70%、丢包<0.1% | ethtool/交换机 |
| 套接字 | TCP Established、UDP pps | P95 < 峰值 80% |
| 应用 | 匹配成功率、房间创建耗时 | <300ms P95 |
| LB | 连接数/失败率/后端状态 | 后端探针失败<1% |
PerfMon 计数器建议:
- \Processor(_Total)\% Processor Time
- \TCPv4\Connections Established
- \UDPv4\Datagrams Received/sec
- \Process(room.exe)\Handle Count / Working Set
- \Network Interface(*)\Packets Outbound Errors
10.2 粗略容量估算(示例)
- 每房间 8 人,平均会话 30 分钟。
- 目标同时在线(CCU) 20,000 人。
- 房间数 ≈ 20,000 ÷ 8 = 2,500 间。
- 单节点承载 400 间(留 20% 余量),需房间节点 ≈ 2,500 ÷ 400 = 6.25 → 7 台。
- 我们先上线 6 台 + 1 台热备(与实际清单一致)。
我上线夜测时,用 PsPing/自研压测把 TCP QPS 顶到 25k/s,UDP pps 顶到 200k/s,25G 口只上去 6–8 Gbps,CPU 40–55%,余量够开服。
11. 压测与联调
11.1 工具
- PsPing(Sysinternals):TCP/UDP 延迟与吞吐。
- Bombardier(HTTP 压测)或 wrk(跑在旁路 Linux/WSL)。
- 自研脚本:模拟玩家登录 → 请求匹配 → 直连房间 → 维持 30 分钟。
11.2 快速命令
# TCP 连接延迟
.\psping.exe -n 1000 103.x.x.10:40000
# UDP 吞吐(发送端)
.\psping.exe -u -l 1200 -n 100000 103.x.x.10:30000
12. 线上故障与坑(当晚真实踩过)
NLB 单播导致交换机广播:同 VLAN 其他机器被“拖慢”。
解决:前端 NIC 独立到 VLAN100,后端业务在 VLAN200,NLB 只在前端;向机房申请 静态 ARP 或改 多播+IGMP。
UDP 粘性不足导致状态丢:Nginx 对 UDP 做 $remote_addr 哈希,公网 NAT 情况下同一出口会让很多玩家被哈到同一后端。
解决:把“真正的亲和”放在 Match API 选址,让客户端拿到直连地址,避免四层 LB 做粘性。
驱动中断调制过强:低延迟抖动大。
解决:把 NIC 的 Interrupt Moderation Rate 调低,打开 RSS 并根据 CPU 核数设置 RSS Queues=8/16。
Windows 更新重启:夜里被策略推更新。
解决:统一 gpedit.msc 配置维护时窗,维护期外禁止自动重启;生产批更新全部走 WSUS/离线。
日志写入打爆系统盘:Kestrel 和应用日志默认 C 盘。
解决:C:\logs 映射到 NVMe,按天滚动 + 压缩 + Filebeat 出口。
13. 回滚与应急剧本(我贴墙上的那张纸)
策略 A:摘除新节点(Consul 标记 maintenance → Match API 不再派号);
策略 B:Nginx 热重载
nginx -t && nginx -s reload
策略 C:切回 NLB 直连(在 DNS 将 game.domain.com 指到 NLB VIP,仅保留最稳定的端口段)。
策略 D:限流/排队(Match API 侧返回“排队位”与重试时间,防止雪崩)。
策略 E:熔断开关:一键把“直连模式”替换成“全走 LB”,反之亦然。
14. 运维脚本片段(能在凌晨救你的那种)
批量开端口 + 性能开关
$tcp=40000..40100; $udp=30000..30100
$tcp | % { New-NetFirewallRule -DisplayName "Room-TCP-$_" -Direction Inbound -Protocol TCP -LocalPort $_ -Action Allow }
$udp | % { New-NetFirewallRule -DisplayName "Room-UDP-$_" -Direction Inbound -Protocol UDP -LocalPort $_ -Action Allow }
netsh int tcp set global rss=enabled
netsh int tcp set global rsc=enabled
发布 Room 服务(NSSM)
nssm install room "C:\apps\room\room.exe" --port-tcp 40000 --port-udp 30000 --affinity "0-15"
nssm set room AppDirectory "C:\apps\room"
nssm set room Start SERVICE_AUTO_START
nssm start room
Match API 拉取健康后端(PowerShell 伪代码)
$healthy = (Invoke-RestMethod http://127.0.0.1:8500/v1/health/service/room) `
| Where-Object {$_.Checks.Status -eq "passing"} `
| ForEach-Object { $_.Service.Address + ":" + $_.Service.Port }
# 根据负载择优(示例:随机/最少连接由应用内实现)
$node = Get-BestNode $healthy
15. 安全加固(别忽视)
- 最小权限:服务账号非 admin,目录 ACL 最小化;
- TLS:外网入口强制 TLS1.2+,HTTP/2 开启;
- Anti-DDoS:机房与云清洗线路接入位预留;
- IP 白名单:运维/RDP 全走堡垒机与管理 VLAN;
- 审计:Windows 事件订阅 + 中央日志(ELK/CloudWatch 等)。
- 16. 成本与延迟(有个大致心里数就不慌)
- 节点数:6(房间)+2(API)+2(LB)= 10 台;
- 机柜功耗:单台 350–450W,满载 500W;10 台 ≈ 5 kW(含交换机+冗余)→ 半柜足够;
- 带宽:外网峰值 5–8 Gbps,后端专网 10–15 Gbps;
- 时延:香港本地 1–3 ms,新加坡 35–45 ms,华南 20–35 ms(实际以你的运营商路由为准)。
17. 验收清单(上线前逐条打钩)
- NLB VIP 可达,端口规则生效,交换机 IGMP/单播配置确认
- Nginx-Stream 配置 nginx -t 通过且可热重载
- Match API 返回直连地址与 token,客户端直连成功率 > 99%
- Consul/监控全绿,核心指标 P95 在阈值内
- 灰度/回滚开关已演练一次
- 日志分盘、备份与保留策略启用
18. 清晨的机房与第一波玩家
天亮前,我在机柜门上贴好了“变更结束”的标签,NLB 的心跳终于稳定成绿色。第一波玩家涌入,Match API 的曲线先抬头又平缓,Nginx 的连接数像海浪一样起伏,房间分配的失败率停在 0.2%。我把手里那杯彻底凉掉的美式一口喝完,给远程的策划发了条消息:“可以开区了。”
机房外的电梯叮的一声,我知道这套在 Windows Server 上打磨出来的匹配与负载方案,今晚又多了几条能救火的命令、几个能复用的脚本,和一段值得写进手册的故事。
附:小抄(速查表)
常用端口
- Match API(HTTPS):443/8443
- Room Server:TCP 40000–40100、UDP 30000–30100
- Consul:8500(HTTP API)
一键重载
nginx -t && nginx -s reload
NLB 查看状态
Get-NlbClusterNode | ft HostName, StatusCode, Draining
快速健康探测(TCP)
Test-NetConnection 103.x.x.10 -Port 40000