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

跨境直播上行优化:香港服务器支撑的回国链路抖动治理、FEC与自适应码率权衡

发布人:Minchunlin 发布时间:2025-09-24 18:49 阅读量:987


深夜两点,湾仔机房的冷风直往机柜缝里钻。双十一预热场刚开,主播那边的聊天窗口开始刷“卡”“糊”“延迟高”。我盯着 Prometheus 的抖动曲线——回国链路的抖动飙到了 35ms、瞬时丢包 2.4%。香港到广州的常规 18ms RTT 化成了“锯齿”。那一刻,我知道靠“玄学重推流”救不回来,必须动刀到传输链路、队列与编码参数。下面是那一夜我在香港两台上行汇聚服务器上,如何把问题一刀刀剖开的全过程。

1)业务场景与目标

场景:主播端(东南亚/华南)→ 香港上行汇聚(转码/抖动治理)→ 回国链路(两路回程)→ 内地播控/CDN 接入 → 观众。

目标:

把上行端到端时延稳定在 800–1200ms 内(含转码与回传)。

链路抖动 95 分位 < 5ms,有效去抖;丢包 95 分位 < 0.5%。

在 1–3% 间歇性丢包下,画面可感卡顿次数 < 2 次/小时。

自适应码率(ABR)与冗余(FEC/ARQ)之间做到合理权衡:保证画质“渐退不崩”。

2)拓扑与回国链路设计

主播端(OBS/FFmpeg SRT) 
   │ UDP/SRT(ARQ)  主推
   │
   ▼
[香港汇聚边缘A]───VRRP/VIP───[香港汇聚边缘B]   ←→ 共享转码池(GPU/CPU)
   │                         │
   ├───────────回国专线/隧道#1(CN2/GIA/GRE)──────────►  内地播控/接入1
   └───────────回国隧道#2(CMI/CTG/GRE)─────────────►  内地播控/接入2


上行协议:主播→香港用 SRT(UDP/ARQ),低时延且对抖动友好。

回国:香港→内地走 双隧道(GRE over 专线或跨网),上面跑 SRT/QUIC(按对端接入支持而定);策略路由做动态降级与切换。

汇聚:两台香港边缘机以 VRRP(keepalived) 提供 VIP,任何一台维护/故障不影响入口。

3)硬件与系统参数(香港汇聚/转码)

3.1 设备与系统(CentOS 7.9,稳定优先)

角色 机型/CPU 内存 系统盘 数据盘 网卡 OS/内核 备注
汇聚边缘A/B AMD EPYC 7313P(16C/32T) 128GB ECC SATA SSD 480G NVMe U.2 3.84TB(如 PM9A3) Mellanox ConnectX-4 Lx 25GbE ×2 CentOS 7.9 + ELRepo kernel 5.4.x NUMA 单路、IPMI 远控
转码节点×2 Intel Xeon Silver 4310 192GB ECC SATA SSD 480G NVMe 1.92TB 10GbE Intel X710 CentOS 7.9 + CUDA 12.x(可选) NVENC/LVVA

为何这么配:

汇聚边缘更看重IO 与网络;NVMe 用于环形缓存/时移缓冲,Mellanox 网卡支持更稳的硬件时间戳与队列调度。

CentOS 7.9 搭配 ELRepo 5.4 LTS 内核是为拿到 BBR/fq_codel/ethtool offload等新特性,同时保持运维栈的稳定性。

3.2 网卡与内核调优

/etc/sysctl.d/99-live.conf:

net.core.rmem_max = 268435456
net.core.wmem_max = 268435456
net.core.netdev_max_backlog = 250000
net.ipv4.udp_rmem_min = 262144
net.ipv4.udp_wmem_min = 262144
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.ip_local_port_range = 10000 65000
net.core.default_qdisc = fq_codel

网卡与中断亲和(确保多队列不互踩):

# 关闭可能影响时延的合并(按链路实际测试微调)
ethtool -K eth0 gro off lro off gso on tso on
# RPS/XPS 按队列绑核
echo ffff > /sys/class/net/eth0/queues/rx-0/rps_cpus
echo ffff > /sys/class/net/eth0/queues/tx-0/xps_cpus
# 提升环形缓冲
ethtool -G eth0 rx 4096 tx 4096

队列治理(入口/出口 fq_codel):

tc qdisc replace dev eth0 root fq_codel limit 800 target 5ms interval 100ms ecn
tc qdisc replace dev eth0 handle 1: root fq_codel target 5ms ce_threshold 1ms

4)上行协议与关键参数(SRT 为主,RTMP 兜底)

4.1 主推:SRT(主播→香港)

参数建议(OBS/FFmpeg 都适用):

参数 建议值 说明
latency 80–120ms 抖动缓冲,过低会“抖壳”,过高拖时延
rcvbuf/sndbuf 4–8MB rmem_max/wmem_max 对齐
tlpktdrop 1 拥塞时丢旧包,保持实时性
lossmaxttl 20–40 最大重传 TTL,抖动剧烈场景放宽
pbkeylen 16/24/32 AES 加密长度,按合规要求

FFmpeg 推流示例(主播侧或边缘编码器):

ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency \
-g 60 -keyint_min 60 -sc_threshold 0 -bf 0 -b:v 6000k -maxrate 6000k -bufsize 3000k \
-c:a aac -b:a 128k -ar 48000 -ac 2 \
-f mpegts "srt://hk-edge.example.com:10080?mode=caller&transtype=live&latency=100&sndbuf=8388608&rcvbuf=8388608&tlpktdrop=1"

4.2 兜底:RTMP(TCP)

跨境偶有 UDP 端口择优问题,某些移动/校园网对 UDP 不友好。

兜底走 RTMP(TCP)仅作保活,大抖动场景会出现长尾卡段,自动降级时触发。

5)香港转码与 ABR 梯度

思路:香港只收一条 mezzanine(高码率 1080p),在边缘转出多档 ABR;回国链路按健康度选择合适档位转发,优先稳帧不黑屏。

档位 分辨率 视频码率 音频 GOP/帧率
1080p 1920×1080 6.0 Mbps AAC 128k GOP=2s / 30fps
720p 1280×720 3.0 Mbps AAC 128k GOP=2s / 30fps
540p 960×540 1.8 Mbps AAC 96k GOP=2s / 30fps
480p 854×480 1.2 Mbps AAC 96k GOP=2s / 30fps
360p 640×360 0.8 Mbps AAC 64k GOP=2s / 30fps

转码(香港):

ffmpeg -re -i srt://:10080?mode=listener&transtype=live&latency=100 \
-filter_complex "\
[0:v]split=4[v1080][v720][v540][v360]; \
[v720]scale=-2:720[v720o]; \
[v540]scale=-2:540[v540o]; \
[v360]scale=-2:360[v360o]" \
-map [v1080] -c:v:0 libx264 -b:v:0 6000k -maxrate:v:0 6000k -bufsize:v:0 3000k -preset veryfast -tune zerolatency -x264opts "no-scenecut" \
-map [v720o] -c:v:1 libx264 -b:v:1 3000k -maxrate:v:1 3000k -bufsize:v:1 1500k -preset veryfast -tune zerolatency \
-map [v540o] -c:v:2 libx264 -b:v:2 1800k -maxrate:v:2 1800k -bufsize:v:2 900k  -preset veryfast -tune zerolatency \
-map [v360o] -c:v:3 libx264 -b:v:3 800k  -maxrate:v:3 800k  -bufsize:v:3 400k  -preset veryfast -tune zerolatency \
-map a:0 -c:a aac -b:a 128k -ac 2 -ar 48000 \
-f tee "[select=v:0,a:0:f=mpegts]srt://cn2-pop:11080?mode=caller&latency=120|\
[select=v:1,a:0:f=mpegts]srt://cn2-pop:12080?mode=caller&latency=120|\
[select=v:2,a:0:f=mpegts]srt://cn2-pop:13080?mode=caller&latency=120|\
[select=v:3,a:0:f=mpegts]srt://cn2-pop:14080?mode=caller&latency=120"

做法:四档同时“热备”往同一内地接入的不同端口发。接入侧或香港的“指挥器”按链路健康切换 哪一档接入到主播的上游 pipeline(见第 7 节)。

6)抖动治理:队列、缓冲与 ARQ/FEC 权衡

6.1 抖动来源与治理手段

微突发(micro-burst):越洋/跨网边界出现,表现为瞬间队列挤压。

治法:汇聚口 fq_codel + 适度 pacing;SRT latency=100±20ms 作为抖动缓冲。

低速率丢包(1–3%):多数由回国出口(或省际段)发散。

治法:SRT ARQ 先顶一层,必要时在 回国隧道上做轻度 FEC(10–15% 冗余),见下表。

UDP 被限/路由不稳:移动/教育网偶发。

治法:兜底 RTMP/TCP 路径 + 自动降档;或切二回程。

6.2 FEC vs ABR 的数学权衡

设原始码率 R,FEC 冗余比 p,有效码率 R' = R * (1 + p)。

当链路容量 C 固定时,R' < C 才不拥塞。

建议:

当丢包 ≤ 1%:不开 FEC,纯 ARQ(SRT)+ latency 100ms 即可。

1–3%:FEC 10–12%(XOR/Pro-MPEG 级别)或放宽 ARQ TTL,并降一档 ABR。

>3% 且持续 10s+:FEC 15–20% + 至少降两档 ABR。

丢包(1s 均值) 抖动 P95 建议 FEC 冗余 ABR 动作 备注
≤0.5% ≤5ms 0 保持 纯 ARQ
0.5–1% 5–10ms 0–8% 保持/酌情降一级 观察 10s
1–3% 10–20ms 10–12% 降一级 稳帧优先
>3% >20ms 15–20% 降两级 触发兜底路径

落地说明:我在回国 GRE 隧道上做轻度 FEC(XOR 块级),保证 1–3% 的随机丢失不炸 GOP;FEC 只在回国段开,主播→香港段维持纯 ARQ 低时延。

7)“链路指挥器”:基于健康度的 ABR 选择与回程切换

7.1 监测指标

mtr/smokeping 到内地接入的 RTT、Jitter、Loss(每 2s 采样)。

SRT 统计(srt-live-transmit --stats 或接入侧 API):重传率、队列时延。

阈值(经验值,按你网路实测微调):

Loss_10s_avg > 1.5% 或 Jitter_P95 > 15ms → 降一级;

持续 30s 无异常 → 尝试升一级;

Loss_5s_avg > 3% → 直接降两级并切回程。

7.2 简化实现(示例脚本思想)

#!/usr/bin/env bash
# check_link.sh: 采集链路健康度 → 写入 etcd/redis → 由切换器订阅
MTR_TARGET="cn2-pop.example.com"
LOSS=$(mtr -rwzc 20 $MTR_TARGET | awk '/^SUM/ {print $3+0}')
JITTER=$(mtr -rwzc 20 $MTR_TARGET | awk '/^SUM/ {print $8+0}') # 最后一列按版本可能是 Javg/Jmax,需对齐
# 写入 KV;省略鉴权
curl -s -XPUT http://127.0.0.1:2379/v2/keys/link/health -d value="{\"loss\":$LOSS,\"jitter\":$JITTER}"

切换器订阅健康度,在香港边缘控制“哪一档端口”暴露给内地接入(四档已在第 5 节持续推送):

# 伪代码:当触发降级时,将 11080 -> 720p 对应的 12080
iptables -t nat -R PREROUTING 1 -p udp --dport 11080 -j REDIRECT --to-ports 12080
# 或者用 haproxy/keepalived + SRT relay,改后端端口并 reload

这样内地接入侧无需重推拉,“主口”一直是 11080,但被重定向到不同档位端口。

8)回国隧道与策略路由

8.1 建隧道(GRE 示意)

ip tunnel add gre-cn2 mode gre remote <CN2_POP_IP> local <HK_EDGE_IP> ttl 255
ip link set gre-cn2 up
ip addr add 10.10.10.1/30 dev gre-cn2
ip route add <INGEST_1_IP>/32 dev gre-cn2

ip tunnel add gre-cmi mode gre remote <CMI_POP_IP> local <HK_EDGE_IP> ttl 255
ip link set gre-cmi up
ip addr add 10.10.10.5/30 dev gre-cmi
ip route add <INGEST_2_IP>/32 dev gre-cmi

8.2 标记与策略路由(回程切换)
# 将不同目的端口(11080/12080/...)标记不同路由表
iptables -t mangle -A OUTPUT -p udp --dport 11{0,2,3,4}80 -j MARK --set-mark 11
iptables -t mangle -A OUTPUT -p udp --dport 12{0,2,3,4}80 -j MARK --set-mark 12

ip rule add fwmark 11 table 11
ip rule add fwmark 12 table 12
ip route add default dev gre-cn2 table 11
ip route add default dev gre-cmi table 12

注意 MTU:GRE 有开销,建议设置 MPEG-TS 包(1316B)+ UDP/IP 头后不碎片。必要时 -pkt_size 1316 并将隧道 MTU 降至 1476/1436(视底层而定)。

9)监控与 SLO:我们如何证明“稳定了”

关键面板:

链路面:RTT/Jitter/Loss(smokeping/mtr-exporter)、SRT 重传率/队列延迟。

业务面:每分钟缓冲事件数、直播端到端延迟、码率档位切换次数。

指标 优化前(均值/P95) 优化后(均值/P95)
回国 RTT 24ms / 38ms 20ms / 27ms
抖动 Jitter 11ms / 35ms 3.2ms / 6.8ms
丢包(1s 均值) 1.6% / 3.1% 0.3% / 0.9%
端到端时延 1600ms / 2400ms 980ms / 1300ms
观众卡顿/小时 6.2 次 1.1 次

这些数字来自那晚至次日午前 6 小时区间;期间有 3 次 ABR 自动降一档,1 次切换回程。

10)常见坑与现场解法

UDP 被限或跨运营商抖动:移动/教育网高峰时段对 UDP 不友好。

解法:自动切 RTMP/TCP 兜底 + 降档;并将主播端引导到更友好的出口(换 Wi-Fi/拉 4G)。

GRO/LRO 关闭过头:全关会让 CPU 飙高、丢包上升。

解法:只在上行口按需关;后台转码口保留 GSO/TSO。

MTU 碎片:GRE + SRT + MPEG-TS 组合若 MTU 设置不当,轻微抖动就“雪崩”。

解法:包长 1316B,隧道 MTU 明确到位,观测 DF/Frag。

FEC 开太大:10%→20% 冗余直接把有效码率抬高,反而挤满链路。

解法:只在回国段开轻度 FEC,且联动 ABR 下切。

单机热升级:换内核/驱动时忘了 VRRP 切从。

解法:双机 VRRP + 升级前“空口切主”,自动化检查流量归零再动手。

11)风控与演练清单(我们真这么做)

11.1 风险矩阵

风险 触发 影响 预案
回程#1 抖动/丢包暴涨 高峰或路由漂移 观众卡顿 自动切回程#2 + ABR 降级
香港边缘单机故障 硬件/升级 中断入口 VRRP 切换 + 流量归零校验
UDP 被限/阻断 运营商策略 断流 启用 RTMP 兜底 + 时延容忍
过度拥塞 FEC/码率叠加 黑屏/爆音 FEC 降级 + 码率下切两档

11.2 演练脚本(每周例行)

切回程演练:人为提升 gre-cn2 的丢包到 3%(tc qdisc add ... netem loss 3%),确认 30s 内切至 gre-cmi,并在 5 分钟后恢复。

ABR 下切演练:注入 loss 1.8% + jitter 18ms,验证 11080 → 12080 映射更替。

单机维护演练:VRRP 手工漂移,核对业务无感,确认流量归零再升级内核。

UDP 兜底演练:iptables 限制 UDP 10080,确认自动切到 RTMP 并回切成功。

12)成本与资源测算(给老板看的速算表)

项目 数量 单价/月(示意) 小计
香港 1U 边缘(25G口、双上联) 2 x 2x
转码节点(含 NVENC) 2 y 2y
回国双回程(100–200Mbps 保底) 2 z 2z
监控/带外 1 0.1z 0.1z
合计     ≈ 4x + 2z + 0.1z

经验:把钱尽量花在回程质量和监控上,硬件多半能“抠”出来。

13)完整配置碎片(可直接抄)

keepalived(VRRP,香港两机)

/etc/keepalived/keepalived.conf(A 为 MASTER,B 为 BACKUP):

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 110
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass <REDACTED>
    }
    virtual_ipaddress {
        192.0.2.10/24 dev eth0
    }
    track_script {
        chk_srt
    }
}

vrrp_script chk_srt {
    script "/usr/local/bin/check_srt.sh"
    interval 2
    fall 2
    rise 3
}

/usr/local/bin/check_srt.sh(端口健康):

#!/usr/bin/env bash
nc -uz 127.0.0.1 10080 2>/dev/null
exit $?

iptables DSCP(隧道内 QoS)
iptables -t mangle -A PREROUTING -p udp --dport 11080 -j DSCP --set-dscp 46  # EF
iptables -t mangle -A PREROUTING -p udp --dport 12080 -j DSCP --set-dscp 34  # AF41

注:公网跨网不一定保 DSCP,但在自建隧道两端可以识别和调度。

凌晨三点半,观众弹幕里“卡”的频次明显下去了,ABR 在 30 分钟内只降了两次,画面稳定、声音不再“爆”。我把 mtr 的抖动曲线和 Alertmanager 的降级记录贴在群里,主播只回了四个字:“稳,就这套。”
机房的风还是冷,但我把外套拉链拉上,盯着 VRRP 的心跳包一闪一灭,像是在看一口“稳态”的心脏。跨境直播的本质不是追求零丢包、零抖动,而是在“不可控”上,搭出一套可预期的有控系统——发生、检测、限流、降级、兜底、回切。
第二天上午回看报表,我给自己留了三条 TODO:

1)把 FEC 模块从 10% 做成自适应(随时延与重传率调宽窄);

2)把指挥器改成 etcd + Lua 的 HAProxy 动态后端,不再靠 iptables;

3)把转码池再拆一台做高刷专用,避免高峰复用抖动。

就这样,这一夜留在了机柜前,也变成了后来每次大促之前的标准动作与“手感”。

目录结构
全文