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

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

发布人:Minchunlin 发布时间:2025-09-26 09:34 阅读量:750


香港柴湾 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

目录结构
全文