
那天是周五晚高峰,直播间同时在线突破预期,单线网络(原本走的国际出口)开始抖动。OBS 推流端显示“Dropping frames”,客服群里炸了。
我们当场把预案打开:把 CN2 / CMIN2 / CU / PCCW 四条线全部拉满,按运营商与目的 AS 分流;通过 BGP 社区 + 本地优先级(local-preference)+ 更具体前缀(more specific)把入站(观众看流)和出站(主播推流)分摊出去。十几分钟后,丢包从 8% 掉到 <0.3%,直播评论区“不卡了”。
下面就是那次落地方案的完整复盘与可复用的配置清单。
1)目标与场景
- 场景:直播平台部署在香港单 Region(低时延面向大陆用户),业务包括 SRT/RTMP 推流、转码、HLS/LL-HLS 分发(香港为源站/中间层,国内边缘 CDN 另配)。
- 目标:通过 多线 BGP(CT/CN2、CMI/CMIN2、CU、PCCW)实现入站与出站的可控分流与快速故障切换,避免晚高峰单线拥塞。
- 约束:操作系统统一 CentOS 7(内核建议升级)、生产无感切换(不中断直播)、变更可回滚、对主播与观众侧“透明”。
2)整体架构(ASCII 拓扑)
拓扑:
┌─────────────── Internet ────────────────┐
│ │
┌───▼───┐ ┌───────▼───────┐ ┌───────▼───────┐ ┌───────▼───────┐
│ CN2 │ │ CMIN2 (CMI) │ │ CU (9929) │ │ PCCW (3491) │
└───▲───┘ └───────▲───────┘ └───────▲───────┘ └───────▲───────┘
│ │ │ │
├──────────── BGP Upstreams (eBGP,BFD) ─────────────────┤
│ │ │ │
┌──────────────── Core ────────────────┐
│ EVPN/VLAN+LACP | TOR x2 (MLAG) │
│ Route-Reflector | Out-of-band Mgmt │
└───────▲───────────────────────▲───────┘
│ │
┌───────┴───────┐ ┌──────┴────────┐
│ Ingest(SRT) │ │ Origin/HLS │
│ Transcode GPU │ │ Cache/Edge │
└───────▲───────┘ └──────▲────────┘
│ │
┌───────┴───────────────┐ ┌───┴──────────┐
│ FRR/BIRD (BGP) │ │ Policy PBR │
│ Healthcheck + ExaBGP │ │(nftables mark)│
└───────────────────────┘ └───────────────┘
3)机房与硬件清单(我们实配过的安全保守选型)
| 角色 | 型号/CPU | 内存 | GPU | 网卡 | 磁盘 | OS | 备注 |
|---|---|---|---|---|---|---|---|
| Ingest/Transcode | AMD EPYC 7443P (24c) | 128GB | NVIDIA T4 ×2 或 A10 ×1 | Mellanox ConnectX-4 Lx 25G ×2 | NVMe 1.92TB ×2 (RAID1) | CentOS 7 + kernel-ml 5.4 | NVENC、SRT/RTMP 入口 |
| Origin/HLS | Intel Xeon 4310 (12c) | 128GB | - | Intel X710 10G ×2 | NVMe 3.84TB ×2 (RAID1) | CentOS 7 + kernel-ml | HLS/LL-HLS 切片与缓存 |
| 网络核心/TOR | Arista/Dell 交换机 | - | - | 25G/10G 上联 | - | - | MLAG,BFD 到上游 |
| 路由器(可用软路由) | 2×通用 x86 | 32GB | - | 25G ×2 | SSD | CentOS 7/FRR | eBGP 对接四家上游 |
为什么 kernel-ml 5.4? CentOS 7 默认内核对 25G/40G 驱动与 GRO/TSO 行为偏老,升级后中断亲和/多队列和BBR效果明显更稳。
4)运营商与地址规划
| 上游 | ASN/产品 | 我们的用法 | Commit | 备注 |
|---|---|---|---|---|
| CN2(CT) | 常见 AS4809(CN2 GIA) | 面向电信观众/主播的入出站主路 | 2–5G 起步 | 对大陆电信 RTT/丢包最友好,贵 |
| CMIN2(CMI) | 常见 AS58453(CMI) | 面向移动用户的主路 | 2–5G | 大陆移动质量更稳 |
| CU(9929 优质线) | AS9929(CU Premium)/AS4837(默认) | 面向联通用户的主路 | 2–5G | 尽量选 9929 |
| PCCW | AS3491 | 兜底与国际 | 1–2G | 跨境回国可作备胎/补充 |
前缀规划(示例,使用 RFC5737 保留网段占位):
- 公网段 A:203.0.113.0/23(观众访问域名 A 的 A 记录)
- 203.0.113.0/24 专供 CN2(more specific 仅对 CN2 广告)
- 203.0.114.0/24 专供 CMIN2/CU(对 CMI、CU 广告;PCCW 仅公告 /23)
- 公网段 B:198.51.100.0/24(主播推流入口,SRT/RTMP)
- 按运营商分池:不同域名解析到不同池,或用同池但 BGP 入站做拆分
要点:入站可用“更具体前缀 + 选择性通告”来控制流量进哪条线;出站靠 local-pref/AS 路径匹配,必要时配合 PBR。
5)BGP 设计要点
5.1 入站分流(观众→我们)
- 对 CN2:仅向 CN2 通告 203.0.113.0/24,其余上游只通告 203.0.113.0/23。大多数路由器偏好更具体前缀,因此电信侧访问趋向走 CN2。
- 对 CMI/CU:同理把 203.0.114.0/24只对 CMI/CU 通告。
- 拥塞/故障:通过 ExaBGP 动态撤回某条线的 /24,保留 /23,由其他上游接管(观众最多几十秒路由收敛影响,通常 <10s)。
5.2 出站分流(我们→观众)
在 FRR/BIRD 中使用 AS-PATH/社区匹配:
- 目的 AS 属于 4134/4809(CT) → 首选 CN2(local-pref 200)
- 目的 AS 属于 9808/58453(CMI/移动) → 首选 CMIN2
- 目的 AS 属于 9929/4837(CU/联通) → 首选 CU
- 其他国际/未知 → 走 PCCW 或性价比最高的一条,local-pref 150
注意:不同上游支持的 BGP 社区(调优入口/出口、黑洞、优先)不一样,上线前向运营商要社区手册。本文示例社区值仅作占位。
5.3 健康检查与快速切换
BFD:对每条 eBGP 会话启用 bfd(如 min_rx/tx 200ms, multiplier 3),链路级故障秒级感知。
业务质量检查:以省会城市拨测(Ping/HTTP 拉流)统计 RTT、抖动、丢包;超过阈值触发 ExaBGP 调整:
- 提高 MED/AS-PATH prepend 或撤回 more specific /24
- 动作可按省/运营商维度执行,避免全网震荡
6)关键配置(可直接落地)
6.1 CentOS 7 内核与网络优化(/etc/sysctl.d/99-tune.conf)
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 250000
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.ipv4.tcp_congestion_control = bbr
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_mtu_probing = 1
net.ipv4.udp_rmem_min = 16384
net.ipv4.udp_wmem_min = 16384
net.core.default_qdisc = fq
配合 irqbalance、多队列 RSS/ RPS;必要时关闭 GRO/LRO 检测是否减少重排。
6.2 FRR(bgpd.conf)——四上游 eBGP
router bgp 65001
bgp router-id 203.0.113.2
no bgp default ipv4-unicast
timers bgp 5 15
bgp log-neighbor-changes
# CT/CN2
neighbor 203.0.113.253 remote-as 4809
neighbor 203.0.113.253 description CT-CN2
neighbor 203.0.113.253 timers 5 15
neighbor 203.0.113.253 bfd
# CMI/CMIN2
neighbor 203.0.113.254 remote-as 58453
neighbor 203.0.113.254 description CMI-N2
neighbor 203.0.113.254 timers 5 15
neighbor 203.0.113.254 bfd
# CU/9929
neighbor 203.0.113.252 remote-as 9929
neighbor 203.0.113.252 description CU-9929
neighbor 203.0.113.252 timers 5 15
neighbor 203.0.113.252 bfd
# PCCW
neighbor 203.0.113.251 remote-as 3491
neighbor 203.0.113.251 description PCCW
neighbor 203.0.113.251 timers 5 15
neighbor 203.0.113.251 bfd
address-family ipv4 unicast
network 203.0.113.0/23
# more specific:按上游有选择地在 route-map 中permit
network 203.0.113.0/24 route-map ANNOUNCE-CT
network 203.0.114.0/24 route-map ANNOUNCE-CMI-CU
neighbor 203.0.113.253 activate
neighbor 203.0.113.254 activate
neighbor 203.0.113.252 activate
neighbor 203.0.113.251 activate
# 出站策略:按目的AS优先哪条线
neighbor 203.0.113.253 route-map OUT-SET-CT out
neighbor 203.0.113.254 route-map OUT-SET-CMI out
neighbor 203.0.113.252 route-map OUT-SET-CU out
neighbor 203.0.113.251 route-map OUT-SET-PCCW out
# 入站选择:防止上游把垃圾路由喂给你
neighbor 203.0.113.253 route-map IN-FILTER in
neighbor 203.0.113.254 route-map IN-FILTER in
neighbor 203.0.113.252 route-map IN-FILTER in
neighbor 203.0.113.251 route-map IN-FILTER in
exit-address-family
ip as-path access-list CT permit _4134$|_4809$
ip as-path access-list CMI permit _9808$|_58453$
ip as-path access-list CU permit _9929$|_4837$
route-map OUT-SET-CT permit 10
set local-preference 200
route-map OUT-SET-CMI permit 10
set local-preference 190
route-map OUT-SET-CU permit 10
set local-preference 190
route-map OUT-SET-PCCW permit 10
set local-preference 150
# 仅 CT 宣告 /24
route-map ANNOUNCE-CT permit 10
match interface eth0
# 仅 CMI/CU 宣告 /24
route-map ANNOUNCE-CMI-CU permit 10
match interface eth1
# 入站过滤(示例)
route-map IN-FILTER permit 10
match ip address prefix-list DEFAULTS
set ip next-hop unchanged
ip prefix-list DEFAULTS seq 5 permit 0.0.0.0/0 le 0
注:ANNOUNCE-* 的“match interface”是一个简易占位,你可改为 neighbor X.X.X.X route-map X out 并在 route-map 里用 set community 实现更细策略;具体 BGP 社区值向上游索要官方表。
6.3 ExaBGP 动态调度(质量差时撤回 /24)
/etc/exabgp/exabgp.env(简化示例):
neighbor 203.0.113.253 {
router-id 203.0.113.3;
local-address 203.0.113.3;
local-as 65001;
peer-as 4809;
api {
processes [ quality ];
}
}
process quality {
run /usr/local/bin/quality_watchdog.py;
}
quality_watchdog.py(伪代码思路):
- 每 5 秒读取拨测结果(如 Redis/Prometheus API);
- 若 CT 丢包 >1% 持续 30s:向 ExaBGP 打印 withdraw route 203.0.113.0/24 next-hop self; 恢复稳定后 announce route ...。
- 同理对 CMI/CU 执行。
6.4 可选:PBR(nftables 标记 + 多路由表)
当需要“应用/端口级别”强制出某条线时(比如 SRT 必走 CN2):
# 标记 SRT/UDP 流量
nft add table inet pbr
nft 'add chain inet pbr mangle { type route hook output priority mangle; }'
nft 'add rule inet pbr mangle udp dport 10000-20000 meta mark set 0x1'
# 策略路由表
ip rule add fwmark 0x1 table 101
ip route add default via 203.0.113.253 dev eth0 table 101 # CN2 下一跳
7)直播栈落地
7.1 Ingest:SRT/RTMP(SRS 示例)
srs.conf 关键片段:
listen 1935; # RTMP
http_api {
enabled on;
listen 1985;
}
srt_server {
enabled on;
listen 10080;
}
vhost __defaultVhost__ {
tcp_nodelay on;
min_latency on;
play {
gop_cache on;
}
publish {
mr { enabled on; } # 合并转推,压低抖动
}
}
7.2 转码(NVENC)
ffmpeg -hwaccel cuda -c:v h264_cuvid -i rtmp://ingest/app/stream \
-filter_complex "[0:v]scale=1280:720:flags=lanczos[v1];[0:v]scale=1920:1080[v2]" \
-map "[v1]" -c:v h264_nvenc -preset p5 -b:v 3000k -maxrate 3500k -bufsize 6000k -g 48 \
-map "[v2]" -c:v h264_nvenc -preset p5 -b:v 6000k -maxrate 7000k -bufsize 12000k -g 48 \
-map a:0 -c:a aac -b:a 128k \
-f hls -hls_time 2 -hls_list_size 6 -hls_flags delete_segments+independent_segments \
/var/hls/stream/index.m3u8
7.3 HLS/LL-HLS(Nginx)
worker_processes auto;
events { worker_connections 65535; }
http {
sendfile on; tcp_nopush on; tcp_nodelay on;
keepalive_timeout 65;
aio threads;
server {
listen 80 reuseport;
location /hls/ {
types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; }
root /var;
add_header Cache-Control "max-age=1";
}
}
}
8)质量拨测与阈值
省会拨测面板(样例数据,单位 ms/%)
| 线路 | 北京 | 上海 | 广州 | 成都 | 丢包(晚高峰) |
|---|---|---|---|---|---|
| CN2 | 36 | 32 | 24 | 45 | 0.2% |
| CMIN2 | 42 | 38 | 29 | 48 | 0.4% |
| CU(9929) | 45 | 35 | 28 | 50 | 0.5% |
| PCCW | 70 | 62 | 56 | 78 | 1.2% |
告警规则(示意)
- 任一线路 1 分钟内丢包均值 > 1% 且 RTT 中位数上升 > 25% → 触发 EXABGP_WITHDRAW(/24)
- 所有线路均 >1% → 开启 HLS 切片扩容 与 跨池调度,DNS 限流(见下)
9)DNS/域名策略(可选但很好用)
- 观众域名:play.example.com
- 电信 LocalDNS → 解析到 A: 203.0.113.x(CN2 池)
- 移动 LocalDNS → 解析到 A: 203.0.114.x(CMI/CU 池)
- 主播推流域名:push-ct.example.com / push-cm.example.com …(显式分线)
- 工具:权威 DNS(PowerDNS/gdnsd/NS1/Route53)+ EDNS Client Subnet(谨慎配置)
有了 DNS 粗分流 + BGP 精调,晚高峰抗压会更稳。
10)上线当天我们踩过的坑(与解决过程)
- 上游不接受 /25:一开始想做更细粒度,结果多家过滤了 /25,最小接受 /24。当场把策略改成 /23 + 两个 /24 组合。
- 社区值不匹配:参考了旧文档发送了 no-export-to-163 一类社区,不生效。打电话找 NOC 拿到了最新社区手册(每家都不同),改完 5 分钟收敛。
- BFD 过敏:min_rx/tx 50ms, multiplier 3 过于激进,偶发抖动导致来回 flap。调成 200ms/3 + 抖动抑制(hold-down 30s)。
- SRT 丢包与 NIC offload:T4 机器上 GRO 打开时 UDP 重排增多,SRT 时延偶抖。对 SRT 接口关闭 gro/lro 解决。
- conntrack 爆表:LL-HLS 小切片 + 大并发,nf_conntrack_max 不够,且 HLS 静态 GET 没必要过 conntrack。对 /hls/ 出口 绕过 conntrack(nft ct state 规则)+ 增大表项。
- ECMP Hash 不稳定:上游做了 per-flow ECMP,源端口固定导致个别流被分到拥塞路径。我们在 SRT 推流侧启用 pbkeylen + transtype 变化端口范围,效果好转。
- MTU 黑洞:CMI 某段链路 MTU 1500 实际被隧道压缩,导致 PMTU 探测失败。启用 tcp_mtu_probing=1 并在该上游接口降 MTU 至 1472,稳定。
11)变更与回滚 Runbook(摘录)
- T-24h:从低峰开始宣告 /24,观察 2 小时;拨测通过。
- T-2h:开启 BFD;ExaBGP 以 只记录不执行 模式跑 30 分钟,无误再放权。
- 上线:逐线路放开 ANNOUNCE-*,Grafana 观众丢包 <0.5% 即视为稳定。
- 回滚:withdraw /24 回到 /23 兜底;关闭 ExaBGP;local-pref 全部 150。
12)成本与容量(粗估)
| 项目 | 单价/区间 | 备注 |
|---|---|---|
| CN2 带宽 | ¥ 300–600 / Mbps | 视供应商与承诺期 |
| CMIN2/9929 | ¥ 150–400 / Mbps | 波动大 |
| PCCW | ¥ 80–200 / Mbps | 国际为主 |
| 25G 专线端口 | ¥ 2k–5k / 月 | 部分含 BFD |
| 服务器(转码) | ¥ 1.5–2.5w / 月 | 含 T4/A10 |
经验法则:电信与移动各占 35–40% Commit,联通 15–20%,PCCW 10–15% 作兜底;高峰按 1.6× 均值 预留。
13)实测对比(上线前后)
| 指标 | 上线前(单线) | 上线后(多线+BGP) |
|---|---|---|
| 晚高峰观众丢包(P50) | 3.5% | 0.4% |
| 直播开始首帧时间 P95 | 3.2s | 1.7s |
| 推流端回报码率抖动 | 频繁 | 偶发 |
| NOC 手工干预 | 每晚 3–5 次 | 偶发(自动化处理) |
14)安全与合规提示
与上游签订 RTBH/黑洞 社区支持,防 DDoS。
观众日志与主播数据分区存储,注意 个人信息与合规。
跨境合规与备案:边缘 CDN 与内地服务分开算,域名解析策略合规评估。
15)夜里两点的那通电话
上线那天,机房的空调出风像永远吹不完的北风。Grafana 的曲线慢慢贴近我们预期的“平线”。凌晨两点,主播群里有人发了句“今天不卡了,兄弟们辛苦”。
我把最后一条 withdraw 变更记录上链,给自己倒了杯凉掉的咖啡。多线 BGP 的意义,大概就在于:当一条路堵了,你能在最短时间打开另一条——而观众只会觉得“今晚的网挺稳”。
附:可直接复用的清单
- 向四家上游索要最新 BGP 社区文档与最小可接受前缀长度
- 申请至少一个 /23,并能分拆成两个 /24
- FRR/BIRD 基础配置、BFD、max-prefix、防御性过滤
- ExaBGP + 质量拨测脚本(按省/运营商维度)
- DNS(EDNS Client Subnet 可选)+ 分线域名
- CentOS 7 kernel-ml、irqbalance、ethtool、sysctl 优化
- SRS/FFmpeg/NGINX HLS 生产参数与监控
- 回滚预案与变更窗口
如果你也准备在香港做面向大陆的直播,中短期内,这套“DNS 粗分流 + BGP 精调 + 动态撤回”的组合拳,依旧是最好用、最不容易踩大坑的工程路径。