上一篇 下一篇 分享链接 返回 返回顶部

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

发布人:Minchunlin 发布时间:2025-08-23 09:01 阅读量:718


香港机房的空调在半夜三点依旧吼个不停,在葵涌机房 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 目标,帮你把上面每一步再“拧到位”

目录结构
全文