港股行情网关毫秒级可用性:香港服务器结合PFC/ECN、BBR v2与多运营商回程选路

香港柴湾 MEGA-i 机房监控屏左上角的p99 延迟突然顶到 40ms,行情撮合边上的风控网关开始告警抖动。那一刻我站在 42U 机柜前,手心是汗,脑子里只有一个目标:把延迟重新按回 p99<20ms,并且失效切换不丢单包,RTO 控制在百毫秒级。
这一夜,我们把机柜里的每一层——PFC/ECN 的无丢网络、BBR v2 的拥塞控制、FRR 的多运营商回程选路——一层层拧紧;而这篇文章,就是我如何把它们落成、踩坑、救火、再复盘的全流程。
场景与目标
场景:港股 UDP 多播行情 → 解析 → 内网(RoCEv2)复制 → TCP/QUIC 网关下发到各地撮合/风控边缘点。
SLO:
- 内网复制延迟 亚毫秒级(单向中位 < 400µs,p99 < 900µs)。
- 出网到华南/华东用户端 p99 < 20ms(香港汇出);拥塞/切换时不丢单包。
- 故障检测 + 切换 < 300ms(BFD + HealthCheck)。
- 约束:操作系统按客户侧规范固定 CentOS 7(不是 7 Stream),需要自编内核以启用 BBR v2 与更细颗粒的 ECN 支持。
物理与网络拓扑(我用的真实“料”)
硬件清单(核心节点)
| 角色 | 机型/配置 | 关键参数 |
|---|---|---|
| 行情解析/网关 x4 | Dell R6525(AMD EPYC 7643 × 1) | DDR4 3200 256GB;ConnectX-6 Dx 25/100G 双口;2×1.92TB NVMe(DL 日志) |
| 内网交换 | Arista 7050X3 系列 | 32×100G;支持 PFC/ETS/ECN;缓冲可配阈值 |
| 边界路由 x2 | ASR1002-X / FRR 辅助 | 双冗余;对接 CMI/PCCW/NTT;BFD |
| 时间同步 | PTP Grandmaster + ToR PHC | ptp4l + phc2sys,全网对齐 < 1µs |
| 带外 | Lantronix + 独立 OOB 交换 | 远程手术刀,救过命 |
NIC 选择 ConnectX-6 Dx 是因为:支持 RoCEv2 + PFC + ECN,且硬中断/时钟稳定;我们后面会启用 mlnx_qos 做优先级与 no-drop。
L2/L3 拓扑概览
- 行情解析与网关节点接入 ToR(双上联 MLAG),RoCEv2 在 VLAN 3900,DSCP=46(高优先),PFC no-drop class 3。
- 出网通过边界对接 CMI(中国移动国际)/PCCW/NTT 三线,eBGP 多宿主 + FRR 做策略,BFD 200/200/3(检测 < 300ms)。
- 内外分流:RoCEv2 与管理/出口物理口分离(不同 PCIe Root Complex),避免 Buffer 干扰。
交换机:PFC/ECN 的“地基”怎么浇
以 Arista EOS 为例(不同平台语法略有差异,思路一致):
! 开 PFC(class 3 无丢),映射 RoCEv2 流量到优先级 3
priority-flow-control mode on
priority-flow-control no-drop 3
! ECN 标记(WRED + ECN),阈值按 100G 口、浅缓冲调优
wred-profile ECN-PROFILE
explicit-congestion-notification
! 简化示意:低阈值 80KB,高阈值 160KB,概率起点 10%
drop-probability 10 max-threshold 160000 min-threshold 80000
traffic-class 3
attach wred-profile ECN-PROFILE
! DSCP/CoS 映射:将 DSCP 46 -> TC3(无丢)
qos map dscp 46 to traffic-class 3
! DCBX 确认对端(NIC)协商
dcbx mode ieee
阈值怎么定?我们按带宽延迟积(BDP)与交换芯片缓冲大小折中:
- ToR 端口 100G,端到端 RTT ~ 15–30µs,BDP ≈ 100G * 30µs ≈ 375KB。
- 为避免过早标记影响吞吐,将 ECN min-th 0.2–0.3×BDP(80–120KB),max-th 0.4–0.5×BDP。
- PFC 作为兜底,仅在 ECN 控制不住的瞬时尖峰才触发,尽量让 ECN 先工作。
服务器侧:CentOS 7 + BBR v2 + RoCEv2 的“混搭”
1)CentOS 7 自编内核开启 BBR v2
说明:BBR v2 在上游主线合入与发行版节奏各异;CentOS 7 原生内核没有,因此我采用 基于 5.10/5.15 LTS 的 bbr2 补丁,打包为 RPM。若条件受限,可先用 BBR(v1) 过渡。
# 1. 基础依赖
yum groupinstall -y "Development Tools"
yum install -y ncurses-devel bc openssl-devel elfutils-libelf-devel \
rpm-build flex bison dwarves
# 2. 准备源码(示例:5.15 + bbr2 补丁,自行放置补丁)
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.XX.tar.xz
tar xf linux-5.15.XX.tar.xz && cd linux-5.15.XX
patch -p1 < ../bbr2.patch # 来自 Google/Cloudflare 分支或自维护分支
# 3. 配置(启用 FQ、ECN、BBR2)
make olddefconfig
scripts/config --enable CONFIG_TCP_CONG_BBR2
scripts/config --module CONFIG_NET_SCHED
scripts/config --enable CONFIG_TCP_CONG_BBR
scripts/config --enable CONFIG_TCP_ECN
make olddefconfig
# 4. 打包 RPM(CentOS 7 推荐)
make -j$(nproc) rpm-pkg
rpm -ivh ../kernel-5.15.*.rpm
grub2-set-default 0 && reboot
启用 FQ 调度 + BBR v2 + ECN:
cat >/etc/sysctl.d/99-lowlat.conf <<'EOF'
# 基础队列建议:BBR* 推荐 FQ
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr2
# ECN 开启(BBR v2 友好)
net.ipv4.tcp_ecn=1
# BBR 队列深度
net.ipv4.tcp_limit_output_bytes=131072
# Buffer 调整(按 100G + 微秒级 RTT)
net.core.rmem_max=134217728
net.core.wmem_max=134217728
net.ipv4.tcp_rmem=4096 1048576 67108864
net.ipv4.tcp_wmem=4096 1048576 67108864
# Busy poll 适度开启(微调)
net.core.busy_read=50
net.core.busy_poll=50
EOF
sysctl --system
验证:sysctl net.ipv4.tcp_congestion_control 显示 bbr2;tc qdisc show 有 fq。
2)NIC 与 IRQ 调优(ConnectX-6 Dx)
# 固件与驱动:用 MLNX OFED 匹配内核版本
# PFC/DSCP 信任与优先级
mlnx_qos -i eth0 --trust dscp
mlnx_qos -i eth0 --pfc 0,0,0,1,0,0,0,0 # class3 开 no-drop
mlnx_qos -i eth0 --prio2buffer 3,3,3,3,3,3,3,3
# 关闭影响延迟的合并特性,保留 TX 健康优化
ethtool -K eth0 gro off lro off gso off tso off rx-udp-gro-forwarding off
ethtool -G eth0 rx 4096 tx 2048
# 中断绑核(避免跨 NUMA)
for q in $(seq 0 31); do
echo $((1<<q)) > /proc/irq/$(grep -i "mlx.*eth0.*TxRx-$q" -R /proc/irq/*/ -l | sed 's/[^0-9]//g')/smp_affinity
done
# tuned
tuned-adm profile latency-performance
3)时间同步(PTP)
yum install -y linuxptp
cat >/etc/ptp4l.conf <<'EOF'
[global]
twoStepFlag 1
tx_timestamp_timeout 10
clock_servo pi
network_transport L2
delay_mechanism E2E
EOF
systemctl enable --now ptp4l
systemctl enable --now phc2sys
内网复制:RoCEv2 + PFC/ECN 的落地要点
- 分类:应用在发送复制流量时打 DSCP=46;ToR 将其映射到 TC3(no-drop)。
- ECN:交换机阈值先行,Linux 端开启 tcp_ecn=1;对 RDMA(RoCEv2)场景,PFC 只是兜底。
- 拥塞回退:遇到 ECN 标记或过多 CNP,应用缩小批次提交(batch size)与环形队列深度,减少长队列。
- 生产要诀:先开 ECN,再按需收紧 PFC,否则极易引发 PFC 风暴(下面“坑”会说)。
出网:FRR 多运营商回程选路(回程优先)
目标:对 CMI / PCCW / NTT 三线,按地区 RTT/抖动动态选择,同时通过 BGP 社区/LOCAL_PREF 影响入站/回程。我们控制不了对端全路径,但可以尽量把返回流量引向当下最好的运营商。
FRR 安装与基础 BGP
yum install -y frr frr-pythontools
systemctl enable --now frr
vtysh <<'EOF'
conf t
router bgp 65010
bgp router-id 10.10.10.1
timers bgp 3 9
neighbor 203.0.113.1 remote-as 58453 ! CMI
neighbor 203.0.113.2 remote-as 3491 ! PCCW
neighbor 203.0.113.3 remote-as 2914 ! NTT
!
! BFD 加速收敛
neighbor 203.0.113.1 bfd
neighbor 203.0.113.2 bfd
neighbor 203.0.113.3 bfd
!
bfd
profile FAST
transmit-interval 200
receive-interval 200
detect-multiplier 3
!
interface eth1
bfd profile FAST
interface eth2
bfd profile FAST
interface eth3
bfd profile FAST
!
ip prefix-list EXPORT-OURNET permit 198.51.100.0/24 le 24
route-map OUT-CMI permit 10
match ip address prefix-list EXPORT-OURNET
set local-preference 200
! set community <根据运营商文档> # 用于入站控制,例如抑制某区域或 prepend
!
route-map OUT-PCCW permit 10
match ip address prefix-list EXPORT-OURNET
set local-preference 180
!
route-map OUT-NTT permit 10
match ip address prefix-list EXPORT-OURNET
set local-preference 150
!
router bgp 65010
neighbor 203.0.113.1 route-map OUT-CMI out
neighbor 203.0.113.2 route-map OUT-PCCW out
neighbor 203.0.113.3 route-map OUT-NTT out
end
write
EOF
动态健康检查 + 选路(我用的“小脚本”)
我用一个 Python 脚本每 5 秒从三张表测量 RTT/p95 抖动/丢包(针对广州、上海、北京三处探针),然后通过 vtysh 调整 local-preference 或者对特定前缀加 AS-prepend 的社区标签。
#!/usr/bin/env python3
import os, subprocess, statistics, time
PROBES = {
'GZ': ['120.XXX.XXX.1', '223.XXX.XXX.2'],
'SH': ['101.XXX.XXX.3', '58.XXX.XXX.4'],
'BJ': ['36.XXX.XXX.5', '60.XXX.XXX.6'],
}
CANDIDATES = {
'CMI': {'if': 'eth1', 'neighbor': '203.0.113.1', 'lp': 200},
'PCCW':{'if': 'eth2', 'neighbor': '203.0.113.2', 'lp': 180},
'NTT': {'if': 'eth3', 'neighbor': '203.0.113.3', 'lp': 150},
}
def ping(dev, dst, cnt=5, timeout=0.5):
cmd = ['ping', '-I', dev, '-c', str(cnt), '-W', str(int(timeout*1000)), dst]
out = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True).stdout
rtts = []
for ln in out.splitlines():
if 'time=' in ln:
rtts.append(float(ln.split('time=')[1].split()[0]))
return rtts
def decide(scores):
# 简单排名:按总分=平均RTT+2*p95 抖动
return sorted(scores.items(), key=lambda x: x[1])[0][0]
def set_lp(neigh, lp):
cfg = f"""
conf t
router bgp 65010
neighbor {neigh} route-map DYN out
exit
route-map DYN permit 10
set local-preference {lp}
end
write
"""
subprocess.run(['vtysh'], input=cfg, text=True)
while True:
results = {}
for name, meta in CANDIDATES.items():
samples = []
for city, targets in PROBES.items():
for dst in targets:
rtts = ping(meta['if'], dst)
if rtts:
p95 = sorted(rtts)[int(len(rtts)*0.95)-1]
samples.append((statistics.mean(rtts), p95))
if samples:
avg = statistics.mean([a for a,_ in samples])
p95j = statistics.mean([b for _,b in samples]) - avg
results[name] = avg + 2*p95j
best = decide(results)
for name, meta in CANDIDATES.items():
set_lp(meta['neighbor'], 220 if name==best else meta['lp'])
time.sleep(5)
实战效果:CMI 在华南往返通常最稳;PCCW 对华东/海外有时更好;NTT 在夜间跨洲传输表现不错。脚本让回程倾向当时最优线路。
网关应用侧的几个关键开关
FQ + BBR v2:出口 qdisc 必须是 FQ,否则 BBR 效果会大打折扣。
连接池/拥塞回退:收到 ECN 标记增多时,应用端下调窗口与批次,避免自制长队列;UDP 多播转 TCP 时,拆分流。
日志:NVMe 顺序写 + O_DIRECT,每批落盘 < 200µs,断电靠写前校验和恢复。
观测与告警(这些指标救过我)
交换机:PFC Rx/Tx 计数器、ECN 标记速率、WRED 丢包。
服务器:softnet_backlog_drops、tcp_ecn_ce、BBR delivery_rate、rtt_us。
BGP:BFD session state、prefix count、route-map 命中。
端到端:Blackbox 对重点区域任播地址的 p50/p95/p99、抖动、丢包。
踩过的坑与现场解法
PFC 风暴(经典坑)
现象:ToR PFC Rx 飙升,端口 pause 挂死,整条 TC3 业务冻住。
原因:ECN 阈值太高 + 应用突发,直接打满缓冲;PFC 提前触发并扩散。
解法:
降低 ECN min/max,让 ECN 更早介入;
应用层限制 batch 与分流;
PFC 只保 class3,其他 class 全丢(避免扩散)。
ECN 标记不生效
现象:交换机有 ECN 标记,主机端 tcp_ecn_ce 计数器不动。
原因:DSCP 映射错误,中间环节把 DSCP 擦掉。
解法:
逐段抓包(交换机 SPAN + 主机 tcpdump -vvv),核对 DSCP/ECT(0/1);
固化接入层 trust dscp 与 mlnx_qos --trust dscp。
BBR v2 模块加载失败
现象:tcp_congestion_control 无法设 bbr2。
原因:编译配置缺 CONFIG_TCP_CONG_BBR2 或符号不兼容。
解法:
modprobe tcp_bbr2 确认模块;
核对 uname -r 与 OFED/驱动版本一致性;
临时回退 BBR(v1),保障业务先稳。
FRR 路由抖动
现象:5 秒周期切换导致上游“翻书”,RTT 反复。
解法:
引入迟滞(hysteresis):连续 N 次劣化才切;
绑定地区粒度:广州差时只对华南前缀加权,避免全局切。
数据对比(上线前后 30 分钟窗口)
端到端(香港 → 华南用户侧)
| 指标 | 优化前 | 优化后 |
|---|---|---|
| p50 RTT | 14.8 ms | 13.6 ms |
| p95 RTT | 28.4 ms | 17.9 ms |
| p99 RTT | 41.7 ms | 19.8 ms |
| 丢包 | 0.045% | 0.002% |
| 故障切换(BFD) | ~1.2 s | < 300 ms |
内网(解析 → 网关 RoCEv2)
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 单向中位 | 780 µs | 360 µs |
| p99 | 1.9 ms | 0.88 ms |
| ECN 标记率 | 低但突发 | 平稳,PFC 基本不触发 |
一次“红灯”处理的全过程(复盘)
T+0s:p99 40ms,CMI 南向波动,脚本给 PCCW 提升 LP,300ms 内切到 PCCW。
T+10s:ECN 标记上扬,微批次缩小 20%,Ring 深度减半。
T+60s:p95 恢复 18ms,p99 压回 22ms;后续脚本观测 1 分钟稳定,再将 CMI LP 拉回,仅南向前缀保持 PCCW。
T+15min:ToR WRED 曲线回落,PFC 计数几乎不动。事件结束。
部署清单(可直接抄)
交换机:启用 PFC(仅 class3)、ECN 阈值按 BDP 配、DSCP 映射核对。
服务器:CentOS 7 自编内核(BBR v2)、fq qdisc、tcp_ecn=1、NIC 关 GRO/LRO、IRQ 绑核、tuned latency-performance。
时间:PTP 全网对齐(<1µs)。
应用:分流 + 可回退批次;拥塞信号接入(ECN/CNP)。
边界:FRR+BFD,三线 eBGP,多地探针脚本动态调 LOCAL_PREF;需要时用 社区做入站引导。
观测:PFC/ECN 计数、BBR 速率/RTT、BFD、端到端 p95/p99;告警阈值带迟滞。
清晨 6:10 的机房门
警报面板回到一片绿色的时候,冷通道的风突然变得“轻”了。走出机房门,天色刚亮,我知道这套毫秒级可用性的骨架已经扎实:
- ECN 先行、PFC 兜底,让内网复制稳在亚毫秒;
- BBR v2 + FQ,让拥塞友好而不牺牲吞吐;
- FRR 多运营商回程,让不确定的公网也能被我们“温柔地”驯服。
这不是一次性“调通”的黑魔法,而是一套能够反复复用的工程化方法论。下一次红灯来时,它仍然会顶住。
附:关键配置与命令速查(摘选)
sysctl(CentOS 7,BBR v2/FQ/ECN)
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr2
net.ipv4.tcp_ecn=1
net.core.rmem_max=134217728
net.core.wmem_max=134217728
net.ipv4.tcp_rmem=4096 1048576 67108864
net.ipv4.tcp_wmem=4096 1048576 67108864
net.core.busy_read=50
net.core.busy_poll=50
ConnectX-6 Dx(PFC/优先级)
mlnx_qos -i eth0 --trust dscp
mlnx_qos -i eth0 --pfc 0,0,0,1,0,0,0,0
ethtool -K eth0 gro off lro off gso off tso off
FRR(BFD/LOCAL_PREF)
bfd profile FAST
transmit-interval 200
receive-interval 200
detect-multiplier 3
!
router bgp 65010
neighbor <CMI> bfd
neighbor <PCCW> bfd
neighbor <NTT> bfd
!
route-map OUT-CMI permit 10
set local-preference 200