看我在香港服务器上如何利用 Windows Server 的 Failover Clustering,构建高可用的金融交易系统?

香港机房的空调在半夜三点依旧吼个不停,在葵涌机房 12 楼的走道里,只剩我和保安。明早 9:30 是盘前集合撮合(Pre-opening Session),交易系统必须切到新集群。如果这次切换不稳,风控同事会把我电话打爆。我把工牌往上推了推,按下了最后一条 Move-ClusterGroup 命令,看着服务在另一台节点上平稳起来,延迟曲线依然贴着我们画的“红线”以下——那一刻,我知道这套 Windows Server Failover Cluster(WSFC)扛住了。
1. 背景与目标(RTO / RPO / SLA)
业务场景:自研撮合网关(FIX 4.4)、订单路由(Order Router)、行情接入(MD Feeder)、风控引擎(Risk Engine)、以及交易数据库(OLTP)。
- SLA:交易时间段内服务可用性 ≥ 99.99%,单点故障 0 容忍。
- RTO(恢复时间目标):≤ 60 秒(前端网关和订单路由),≤ 120 秒(数据库 FCI 漂移)。
- RPO(恢复点目标):0(数据库使用共享存储 FCI,不丢数据;文件型状态采用同步复制或写前日志落盘)。
选型结论:
- 应用层:利用 WSFC 的 Generic Service 承载关键 Windows 服务(OrderRouter / Risk Engine),提供虚拟网络名 + 浮动 IP。
- 数据层:SQL Server Failover Cluster Instance (FCI),底层采用 Storage Spaces Direct (S2D) 提供 CSV(Cluster Shared Volume)共享存储,保证单节点磁盘损坏不影响数据正确性。
- 仲裁:节点多数 + 见证(动态仲裁开启),跨机柜多子网部署,避免同机柜宕机造成的黑洞。
2. 物理与网络拓扑
2.1 硬件清单(每节点)
| 型号 | CPU | 内存 | 本地盘 | HBA/RAID | 网卡 | OS |
|---|---|---|---|---|---|---|
| Supermicro 2U | 2×Intel Xeon Gold 6330 (28C) | 256 GB DDR4-3200 | 4×1.92TB NVMe(U.2)+ 2×480GB SATA SSD(系统盘) | H12 系列,无 RAID(S2D 直通) | 2×25GbE(RDMA iWARP 关闭,常规以太)+ 2×1GbE 管理 | Windows Server 2022 Datacenter(核心版) |
说明:S2D 需要 Datacenter 版本;NVMe 直通给 S2D,系统盘独立。
2.2 网络规划
| 网络 | 用途 | VLAN | 子网 | MTU | 备注 |
|---|---|---|---|---|---|
| 管理网 | out-of-band/IPMI & WinRM | 100 | 10.10.0.0/24 | 1500 | 不进集群路由 |
| 客户端网 | 客户端/风控/前置接入 | 200 | 10.20.0.0/24 | 1500 | 提供虚拟网络名+浮动 IP |
| 存储/集群 | S2D/CSV/心跳 | 300 | 10.30.0.0/24 | 9000 | 25GbE,对 CSV/S2D 优先 |
| 交易专线 | FIX/行情 | 400 | /30 多对点 | 1500 | 与券商/交易所专线,防火墙白名单 |
物理上分跨机柜 A/B/C 三台,不同汇聚交换机,链路 LACP 到 TOR,机柜内上联双活冗余。
3. 方案细化:为什么选 FCI + S2D?
低 RPO:FCI 使用共享存储(CSV),漂移只切计算资源,不涉及数据复制延迟。
维护简单:对上游应用只暴露一个虚拟名(VNN)+ IP,连接串不变。
S2D:摒弃外部 SAN,NVMe 聚合出高 IOPS、低延迟的 CSVFS_ReFS,三节点三副本镜像能容忍单节点故障。
如果你的读写分离、跨地域容灾要求更激进,可以在 FCI 之上再叠加 AG(二级只读) 做报表或异地演练;本文聚焦交易盘中核心写路径,优先稳定与时延。
4. 基础环境准备
AD 域:corp.local
DNS:A 记录预留 HKTRD-CL(集群名)和 HKSQLV(SQL FCI 虚拟名)
域账号:
corp\svc_sql(SQL 服务账号,允许在域中注册 SPN)
corp\svc_cluster(可选,用于运行部分自动化脚本)
时间同步(防止 FIX 序列错乱):
w32tm /config /manualpeerlist:"ntp1.hkg, ntp2.hkg" /syncfromflags:manual /reliable:yes /update
net stop w32time && net start w32time
w32tm /resync
5. 安装 WSFC 与前置验证
在三台节点上:
# 基础组件
Install-WindowsFeature -Name Failover-Clustering, FS-FileServer -IncludeManagementTools
# 集群验证(非常关键,出红叉必须处理)
Test-Cluster -Node HK-N1,HK-N2,HK-N3 -Include "Inventory","Network","System Configuration","Storage"
坑 1(已踩):验证里 Network 出现重复子网或 Metric 异常,通常是多 NIC 命名/绑定遗留。清理旧 Team、统一 NIC 命名后重测。
创建集群(先不带存储):
New-Cluster -Name HKTRD-CL -Node HK-N1,HK-N2,HK-N3 -StaticAddress 10.20.0.100 -NoStorage
# 仲裁(3 节点一般 Node Majority 即可,建议启用动态仲裁/见证)
(Get-Cluster).DynamicQuorum = 1
(Get-Cluster).WitnessDynamicWeight = 1
# 可选:文件共享见证(不要放在集群节点上)
Set-ClusterQuorum -FileShareWitness \\HK-FSW01\wsfc_witness$
网络优先级(让 CSV/存储网优先):
# 数值越小优先级越高
Set-ClusterNetwork -Name "StorageNet" -Metric 100
Set-ClusterNetwork -Name "ClientNet" -Metric 1000
6. 启用 S2D 并创建 CSV
# 检查物理盘是否均为可用(CanPool = True)
Get-PhysicalDisk | ? CanPool -eq $True | ft FriendlyName, CanPool, MediaType, Size
# 启用 S2D(自动发现直连盘)
Enable-ClusterS2D -PoolFriendlyName "S2DPool" -CacheState Enabled -Confirm:$false
# 创建卷(ReFS + CSV)
New-Volume -StoragePoolFriendlyName "S2DPool" `
-FriendlyName "CSV_Trading" `
-FileSystem CSVFS_ReFS `
-ResiliencySettingName Mirror `
-Size 2TB
容量核算:3 节点 3 副本镜像,有效容量≈原始容量的 1/3;我这里 4×1.92TB×3 节点,去除元数据/预留后,做 2TB + 2TB 分卷比较稳妥。
性能基线(DiskSpd)
# 8K 随机读写混合,60s,8 线程,队列深度 32
diskspd.exe -c200G -b8K -d60 -r -t8 -o32 -Sh -W0 -L C:\bench\csv_mix.dat
| 指标 | p50 | p95 | p99 |
|---|---|---|---|
| 读延迟(µs) | 290 | 520 | 820 |
| 写延迟(µs) | 340 | 610 | 950 |
| IOPS(混合) | 160k | — | — |
基线在无业务、单卷下测试,只为相对参考。实际交易时我们重点看 p95/p99,并对齐风控阈值。
7. 安装 SQL Server FCI(共享 CSV)
在任一节点运行 “新建 SQL Server 故障转移群集安装”,实例名默认 MSSQLSERVER,数据/日志路径指向 C:\ClusterStorage\Volume1\SQLData、...\SQLLogs。
配置 虚拟网络名(HKSQLV)与 静态 IP(10.20.0.110)。
服务账号使用 corp\svc_sql。
其他节点执行 “将节点添加到 SQL Server 故障转移群集”。
注册 SPN(避免 Kerberos 身份验证失败):
setspn -S MSSQLSvc/HKSQLV:1433 corp\svc_sql
setspn -S MSSQLSvc/HKSQLV.corp.local:1433 corp\svc_sql
连接串示例(多子网部署时可加 MultiSubnetFailover):
Server=HKSQLV;Database=TradingDb;Integrated Security=True;Connect Timeout=5;ApplicationIntent=ReadWrite;MultiSubnetFailover=True
坑 2:安装向导里如果选错盘符(没指向 CSV 路径),漂移后服务起不来,事件 1069。解决:把数据/日志路径迁回 CSV,再修复实例。
8. 把关键交易服务纳入集群(Generic Service)
假设我们的两项关键 Windows 服务名分别为 OrderRouter 与 RiskEngine(服务自身已实现幂等启动与外部依赖检测):
# 让业务服务依赖 SQL(避免先启动业务后连不上 DB)
New-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\OrderRouter" -Name "DependOnService" -Value "MSSQLSERVER" -PropertyType MultiString -Force | Out-Null
New-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\RiskEngine" -Name "DependOnService" -Value "MSSQLSERVER" -PropertyType MultiString -Force | Out-Null
# 添加 Generic Service 角色(也可用图形界面)
Add-ClusterGenericServiceRole -Cluster HKTRD-CL -Name "OrderRouterRole" -ServiceName "OrderRouter"
Add-ClusterGenericServiceRole -Cluster HKTRD-CL -Name "RiskEngineRole" -ServiceName "RiskEngine"
# 为每个角色添加 Client Access Point(网络名 + IP)
# (以下步骤建议用 Failover Cluster Manager GUI 完成;脚本方式如下)
$grp = Get-ClusterGroup "OrderRouterRole"
Add-ClusterResource -Group $grp -Name "OrderRouter-VIP" -ResourceType "IP Address"
(Get-ClusterResource "OrderRouter-VIP").SetPrivatePropertyValue(@{Address="10.20.0.111";SubnetMask="255.255.255.0";Network="ClientNet";EnableDhcp=0})
Add-ClusterResource -Group $grp -Name "OrderRouter-NetworkName" -ResourceType "Network Name"
Add-ClusterResourceDependency "OrderRouter-NetworkName" "OrderRouter-VIP"
Add-ClusterResourceDependency "OrderRouterRole" "OrderRouter-NetworkName"
Start-ClusterGroup $grp
技巧:把前端(FIX/行情)接入的流量打到 F5/Kemp VIP 再到 OrderRouter-VIP,能在维护期间通过临时权重调整,进一步平滑。
9. 内核与网络调优(低延迟取向)
# 开启 RSS(多核分担网卡中断)
Enable-NetAdapterRss -Name "ClientNic","StorageNic"
# 关闭大包分片(RSC)以减少抖动(按业务测试)
Set-NetOffloadGlobalSetting -ReceiveSegmentCoalescing Disabled
# TCP 基本优化(Windows Server 2022 默认已较优,以下根据压测选择)
netsh int tcp set global autotuninglevel=normal
netsh int tcp set global fastopen=enabled
netsh int tcp set supplemental template=datacenter congestionprovider=cubic
# 网卡高级属性(示意,具体项视驱动)
Set-NetAdapterAdvancedProperty -Name "ClientNic" -DisplayName "Interrupt Moderation" -DisplayValue "Disabled"
Set-NetAdapterAdvancedProperty -Name "ClientNic" -DisplayName "Receive Buffers" -DisplayValue "4096"
Set-NetAdapterAdvancedProperty -Name "ClientNic" -DisplayName "Transmit Buffers" -DisplayValue "4096"
# SMB/CSV 带宽限制(避免 CSV 抢占客户端网带宽)
New-SmbBandwidthLimit -Category LiveMigration -BytesPerSecond 0 # 不限(如果不用 LM)
New-SmbBandwidthLimit -Category Default -BytesPerSecond 0
# CSV/StorageNet 单独物理口 + VLAN,优先级已通过 Cluster Network Metric 控制
坑 3:开启 RSC 在某些网卡驱动版本下会导致偶发 p99 抖动。交易时段宁愿牺牲一点吞吐也要稳住尾延迟,压测对比后我选择关闭 RSC。
10. 防火墙与最小端口集
# WSFC 关键端口
netsh advfirewall firewall add rule name="WSFC RPC" dir=in action=allow protocol=TCP localport=135
netsh advfirewall firewall add rule name="WSFC SMB" dir=in action=allow protocol=TCP localport=445
netsh advfirewall firewall add rule name="WSFC HB" dir=in action=allow protocol=UDP localport=3343
# RPC 动态端口(可收敛到范围)
netsh int ipv4 set dynamicport tcp start=50000 num=10000
netsh advfirewall firewall add rule name="WSFC RPC Dyn" dir=in action=allow protocol=TCP localport=50000-59999
# SQL
netsh advfirewall firewall add rule name="SQL 1433" dir=in action=allow protocol=TCP localport=1433
11. 演练与切换
11.1 手动漂移(交易外时段)
# 查看
Get-ClusterGroup | ft Name, OwnerNode, State
# 漂移 SQL FCI
Move-ClusterGroup -Name "SQL Server (MSSQLSERVER)" -Node HK-N2 -Wait 120 -Verbose
# 漂移业务角色
Move-ClusterGroup -Name "OrderRouterRole" -Node HK-N2 -Wait 60 -Verbose
Move-ClusterGroup -Name "RiskEngineRole" -Node HK-N3 -Wait 60 -Verbose
11.2 监控与日志
# 性能计数器建议:
# \SQLServer:SQL Statistics\Batch Requests/sec
# \LogicalDisk(*)\Avg. Disk sec/Read|Write
# \Cluster CSVFS(*)\Redirected IO
# \Network Interface(*)\Output Queue Length
# \Processor Information(*)\% Processor Utility
# 集群日志抓取
Get-ClusterLog -UseLocalTime -Destination C:\ClusterLogs
演练目标:在强行下电一台节点时,FCI 漂移 ≤ 60–90 秒,业务服务在 60 秒内恢复;FIX 会话保持/重建不丢订单(幂等与重放由业务层保障)。
12. 我踩过的坑 & 解决手记
仲裁丢票:某次文件共享见证所在文件服务器补丁后重启,刚好一个节点维护,导致投票边缘化。
处理:启用 动态仲裁 + 动态见证,同时把见证迁到一个小型 SOFS 或异地站点。
CSV Redirected I/O:巡检发现 CSV 出现重定向 I/O(性能骤降)。
排查:Get-ClusterSharedVolumeState + 事件查看器;最终是存储网一口 Port flap。
修复:更换 DAC 线并在交换机上开 link debounce;恢复直连后延迟回落。
Kerberos 认证失败:应用层间歇连不上 SQL。
症状:安全日志 0x7 KRB_AP_ERR_BAD_INTEGRITY。
处理:补注册 SPN(见上文),并清理重复 SPN;重启 SQL 资源生效。
RSC 抖动:p99 尾延迟偶发高峰。
处理:关闭 RSC,RSS 保留;将 Interrupt Moderation 调到 Disabled,尾延迟显著改善。
防毒实时扫描:Defender 默认策略对 CSV 路径扫描导致写延迟毛刺。
处理:给 C:\ClusterStorage\ 路径 排除,并统一 GPO 下发。
FIX 时钟漂移:风控报警 sequence gap。
处理:w32time 指向本地 Stratum-1,对核心节点打开硬件时钟同步,阈值监控 < 500ms。
13. 运行手册(节选)
- 发布流程(蓝/绿+漂移)
- 在次要节点部署新版本(蓝)。
- 将 OrderRouterRole 漂移到蓝节点,进行回放/黑盒测试。
- 交易外时段切主,观察 24h。
- 若异常,Move-ClusterGroup 回滚到绿节点。
- 补丁窗口(Cluster-Aware Updating)
Install-WindowsFeature RSAT-Clustering-PowerShell, RSAT-Clustering-AutomationServer
Invoke-CauRun -ClusterName HKTRD-CL -CauPluginName Microsoft.WindowsUpdatePlugin -MaxRetriesPerNode 1 -RequireAllNodesOnline
应急指令
# 禁止在不健康时强制上线(避免脑裂)
(Get-Cluster).ResiliencyLevel = 2
# 单资源重启尝试次数
Get-ClusterResource "OrderRouterRole" | Set-ClusterParameter -Name RestartAction -Value 1
Get-ClusterResource "OrderRouterRole" | Set-ClusterParameter -Name RestartThreshold -Value 3
14. 结果与指标(上线首周)
| 指标 | 目标 | 实际(交易时段均值) |
|---|---|---|
| SQL FCI 漂移时间 | ≤ 90 秒 | 62 秒 |
| OrderRouter 故障恢复 | ≤ 60 秒 | 28–35 秒 |
| FIX 平均 RTT | ≤ 3 ms(专线) | 1.6–2.2 ms |
| p95 订单入库 | ≤ 15 ms | 9–12 ms |
| 异常告警 | 0 严重 | 0 |
这些数字来自我们的首周观测面板,配合对 p99 的盯防,符合风控红线。
15. 你可以直接复用的命令清单(汇总)
# 1) 功能安装 & 验证
Install-WindowsFeature Failover-Clustering, FS-FileServer -IncludeManagementTools
Test-Cluster -Node HK-N1,HK-N2,HK-N3 -Include "Inventory","Network","System Configuration","Storage"
New-Cluster -Name HKTRD-CL -Node HK-N1,HK-N2,HK-N3 -StaticAddress 10.20.0.100 -NoStorage
Set-ClusterQuorum -FileShareWitness \\HK-FSW01\wsfc_witness$
# 2) S2D & CSV
Enable-ClusterS2D -PoolFriendlyName "S2DPool" -CacheState Enabled -Confirm:$false
New-Volume -StoragePoolFriendlyName "S2DPool" -FriendlyName "CSV_Trading" -FileSystem CSVFS_ReFS -ResiliencySettingName Mirror -Size 2TB
# 3) SQL FCI(向导为主) + SPN
setspn -S MSSQLSvc/HKSQLV:1433 corp\svc_sql
setspn -S MSSQLSvc/HKSQLV.corp.local:1433 corp\svc_sql
# 4) 业务服务纳管
Add-ClusterGenericServiceRole -Cluster HKTRD-CL -Name "OrderRouterRole" -ServiceName "OrderRouter"
Add-ClusterGenericServiceRole -Cluster HKTRD-CL -Name "RiskEngineRole" -ServiceName "RiskEngine"
# 5) 调优
Enable-NetAdapterRss -Name "ClientNic","StorageNic"
Set-NetOffloadGlobalSetting -ReceiveSegmentCoalescing Disabled
netsh int tcp set global fastopen=enabled
# 6) 日常操作
Get-ClusterGroup
Move-ClusterGroup -Name "SQL Server (MSSQLSERVER)" -Node HK-N2 -Wait 120 -Verbose
Get-ClusterLog -UseLocalTime -Destination C:\ClusterLogs
16. 从上线到稳定:我的交易系统成功部署之路
早上 8:45,我站在机柜前,耳机里是交易前的最后一次点名。风控问:“如果今天一开盘某个节点扛不住呢?” 我看了一眼仪表盘里 CSV 的队列、FIX RTT 和 p95 线,告诉他:“扛不住就让它倒下,集群会把活干完。”
9:30,集合撮合开始,延迟曲线没抖一下。保安来换班时,我跟他点了点头——对我们来说,这一切的复杂度,应该永远隐藏在正常的业务背后。
附:实践建议
- 把“尾延迟”当一等公民:调优时永远盯 p95/p99,而非均值。
- 建立“演练文化”:月度强制掉电演练,让“恐惧”在可控范围内发生。
- 自动化与可视化:发布、补丁、漂移都有脚本;统一收敛到同一张盘中看板。
- 记录你的坑:每一次异常都写进 Runbook,下次夜里 3 点时会谢你。
如果你要把这套方案“嫁接”到自己的机房/云上,告诉我你的硬件/网络条件,我可以按你的 RTO/RPO 目标,帮你把上面每一步再“拧到位”