直播平台如何利用香港服务器的CN2专线和国际大带宽线路保障国内和东南亚观众“秒开”?

我真正下定决心把香港直播集群“拉到极致”,是在一个周五晚高峰。那晚活动间同时在线超过 18 万,东南亚一切顺滑,国内北上广“秒开率”却从 97% 掉到 91%,弹幕里开始有人吐槽“要么黑屏要么卡在转圈”。我在将军澳机房的走道里,抱着笔电靠着机柜门当工位,一边看 mtr 曲线一边跟线路商打电话,最后定位到问题:国内回程绕了 163,抖动 60+ms。那晚我们临时切到 CN2 GIA 口、调低 GOP、上了 HTTP-FLV 的 GOP 缓存,秒开率几分钟内回到 98%+。
这篇文章就是把当晚的“救火”固化为体系化方案:用香港双口(CN2 + 国际大带宽)+ 策略路由 + SRS/Nginx 播放栈 + GeoDNS/HTTPDNS 调度 + 一揽子内核与编码优化,把“秒开”变成日常。
目标与指标
- 核心目标:国内与东南亚用户首帧时间 ≤ 1.5s,95 线“秒开”(≤ 1s)≥ 97%,卡顿率(rebuffer ratio)≤ 1.5%。
- 约束环境:操作系统 CentOS 7(用户侧有该要求),上新内核启用 BBR。
- 协议栈:推流 RTMP;播放 HTTP-FLV(Web)/HLS(iOS);对极低延迟间使用 WebRTC(可选)。
网络路径:
- 面向国内:China Telecom CN2 GIA(低抖动,低丢包,回程可控)。
- 面向东南亚:国际大带宽口(如 PCCW/NTT/Tata/Equinix Fabric 互联)。
- 调度方式:GeoDNS + HTTPDNS 双轨,优先本地解析,其次 App 走 HTTPDNS 兜底。
参考架构(文字版)
[主播端] --RTMP--> [Ingress 边缘(SRS Origin, HK)]
| 本地GPU/CPU转码
|---> [SRS Edge 集群, HK]
|---> [Relay 节点, 新加坡/曼谷 (可选)]
\
\--(HTTP-FLV/HLS via Nginx)-> 观众
[DNS 调度] GeoDNS/HTTPDNS -> 国内解析到 CN2 IP;东南亚解析到国际口 IP
[网络] 同一台或同集群服务器双上联:CN2 (ens33) + 国际口(ens34)
[路由] 基于源地址的策略路由,确保流量从对应上联进出
[监控] Prometheus + node_exporter + blackbox_exporter + SRS API
资源与规划
1) 机型与参数(示例)
| 角色 | 机房 | 线路 | 型号 | CPU | 内存 | 存储 | GPU | 网卡 | 峰值并发 |
|---|---|---|---|---|---|---|---|---|---|
| Ingress/Origin | HK | CN2 + Intl | 2U | 2×Xeon 6248R | 192GB | 2×1.92TB NVMe | 1×T4(选) | 2×10GbE | 推流 5k, 观众 30k |
| Edge-1 | HK | CN2 | 1U | 1×EPYC 7543P | 128GB | 2×1.92TB NVMe | - | 2×10GbE | 5–8 万并发 |
| Edge-2 | HK | Intl | 1U | 1×EPYC 7543P | 128GB | 2×1.92TB NVMe | - | 2×10GbE | 5–8 万并发 |
| Relay-SG | 新加坡 | Intl | 1U | 1×EPYC 7443P | 64GB | 2×960GB NVMe | - | 2×10GbE | 3–5 万并发 |
备注:T4 仅用于高并发转码(NVENC),纯转发可不配。
2) 网络与策略路由规划(示例)
| 接口 | 用途 | 线路 | 公网 IP | 路由表 ID | 备注 |
|---|---|---|---|---|---|
| ens33 | 国内播放/推流回源 | CN2 | 203.0.113.10/29 | 10 | 国内 A 记录解析到此 IP |
| ens34 | 东南亚播放 | Intl | 198.51.100.20/29 | 20 | 东南亚 A 记录解析到此 IP |
| lo:flv-cn | 绑定服务 | CN2 回源/回包 | 203.0.113.11 | - | 仅本地绑定 |
| lo:flv-sea | 绑定服务 | Intl 回源/回包 | 198.51.100.21 | - | 仅本地绑定 |
系统初始化(CentOS 7)
目标:上新内核、BBR、文件句柄与队列、IRQ/队列绑定,保证在高并发下不“憋气”。
升级内核(ELRepo kernel-ml 5.x)并启用 BBR
yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install -y kernel-ml
grub2-set-default 0 && grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
内核参数(/etc/sysctl.d/99-live.conf)
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 262144
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq
# 多路径下返程安全
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
sysctl --system
文件句柄与进程数
echo '* soft nofile 1048576
* hard nofile 1048576
* soft nproc 65535
* hard nproc 65535' > /etc/security/limits.d/99-nofile.conf
IRQ 亲和与 RPS/XPS(以 10GbE 为例)
# 绑定网卡中断到特定 CPU,避免抖动(示意)
for i in /proc/irq/*/eth*/; do echo 2 > $i/smp_affinity; done
# 打开 RPS(按队列数调整)
echo ffffffff > /sys/class/net/ens33/queues/rx-0/rps_cpus
echo ffffffff > /sys/class/net/ens34/queues/rx-0/rps_cpus
双上联策略路由(确保进出对口)
要点:基于源地址的策略路由最稳。国内用户解析到 CN2 的 IP,请求到达后服务端用该“本机源地址”回复,自然通过 CN2 表走 CN2 口;东南亚同理。
创建路由表(/etc/iproute2/rt_tables)
10 cn2
20 intl
静态默认路由
ip route add default via 203.0.113.1 dev ens33 table cn2
ip route add default via 198.51.100.1 dev ens34 table intl
策略规则(根据本机源地址)
ip rule add from 203.0.113.10/32 table cn2
ip rule add from 198.51.100.20/32 table intl
ip rule add from 203.0.113.11/32 table cn2
ip rule add from 198.51.100.21/32 table intl
持久化(/etc/rc.local)
#!/bin/bash
ip route add default via 203.0.113.1 dev ens33 table cn2
ip route add default via 198.51.100.1 dev ens34 table intl
ip rule add from 203.0.113.10/32 table cn2
ip rule add from 198.51.100.20/32 table intl
ip rule add from 203.0.113.11/32 table cn2
ip rule add from 198.51.100.21/32 table intl
chmod +x /etc/rc.d/rc.local
坑 1:rp_filter 默认为 1 会丢非对称返回包,务必设置为 2(loose)。
坑 2:MTU 不一致会导致分片重传,先 ping -M do -s 1472 目标 校验路径 MTU。
播放栈部署(SRS + Nginx)
1) 安装 SRS(Simple Realtime Server)
yum groupinstall -y "Development Tools"
yum install -y git cmake gcc-c++ openssl-devel
cd /opt && git clone https://gitee.com/ossrs/srs.git
cd srs/trunk
./configure --jobs=4 --with-hls --with-http-server --with-nginx --with-ssl
make -j4 && make install
2) SRS 核心配置(/usr/local/srs/conf/srs.conf,精简示例)
listen 1935;
max_connections 100000;
daemon on;
srs_log_tank file;
srs_log_file /var/log/srs.log;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
tcp_nodelay on;
min_latency on;
# 启动 GOP 缓存,HTTP-FLV 首屏秒开关键
gop_cache on;
queue_length 10;
# RTMP 推流入口
chunk_size 60000;
# HLS(iOS)
hls {
enabled on;
hls_path ./objs/nginx/html/hls;
hls_fragment 1; # 1s 切片
hls_window 3;
hls_cleanup on;
}
# HTTP-FLV(Web)
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
hstrs on; # 减少启动请求
}
# 转码(示例 1080p -> 720p/480p ABR)
transcode live {
enabled on;
ffmpeg /usr/bin/ffmpeg;
engine h264_720p {
vfilter {vfps=25,scale=1280:-2}
vcodec libx264
vbitrate 2500
vprofile main
vpreset veryfast
vparams {keyint=50:sc_threshold=0} # 2s GOP,保障秒开
acodec aac
abitrate 128
asample_rate 44100
channel 2
output rtmp://127.0.0.1/live/[stream]_720p;
}
engine h264_480p {
vfilter {vfps=25,scale=854:-2}
vcodec libx264
vbitrate 1200
vprofile baseline
vpreset veryfast
vparams {keyint=50:sc_threshold=0:tune=zerolatency}
acodec aac
abitrate 96
output rtmp://127.0.0.1/live/[stream]_480p;
}
}
}
要点:
- gop_cache on 让新观众一连上就拿到最近的关键帧,HTTP-FLV 秒开基本靠它。
- keyint ≈ 2s、sc_threshold=0 禁用场景切换强制关键帧,避免 HLS 首切到非关键帧黑屏。
- tune=zerolatency 降低编码缓冲。
3) Nginx(前置 HTTP/2/TLS 与静态 HLS)
yum install -y nginx
/etc/nginx/nginx.conf(关键段)
worker_processes auto;
events { worker_connections 65535; }
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
client_max_body_size 10m;
# 绑定到两张网卡各自 IP(两个 server 块)
upstream srs_flv {
server 127.0.0.1:8080;
}
server {
listen 80;
server_name cn.example.com;
listen 443 ssl http2;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# 只在 CN2 IP 上监听
listen 203.0.113.10:80;
listen 203.0.113.10:443 ssl http2;
location /hls/ {
root /opt/srs/trunk/objs/nginx/html;
add_header Cache-Control no-cache;
}
location ~ \.flv$ {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://srs_flv;
}
}
server {
listen 198.51.100.20:80;
listen 198.51.100.20:443 ssl http2;
server_name sea.example.com;
location /hls/ {
root /opt/srs/trunk/objs/nginx/html;
add_header Cache-Control no-cache;
}
location ~ \.flv$ {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://srs_flv;
}
}
}
坑 3:Nginx 代理 HTTP-FLV 要确保 proxy_http_version 1.1 与长连接,且关闭不必要的压缩,避免 chunked 断流。
调度:GeoDNS + HTTPDNS
DNS 受运营商劫持或跨网解析不准时,HTTPDNS 兜底非常关键。我们同时提供两个域名:
- cn.example.com → 解析到 203.0.113.10(CN2)
- sea.example.com → 解析到 198.51.100.20(Intl)
App 内优先走 HTTPDNS 接口拿到最优 IP,浏览器端走 GeoDNS。
1) 轻量 HTTPDNS(OpenResty/Lua 示例)
server {
listen 8081;
server_name httpdns.example.com;
location /resolve {
default_type application/json;
content_by_lua_block {
local cjson = require "cjson.safe"
local ip = ngx.var.arg_ip or ngx.var.remote_addr
-- 简化:根据 GeoIP 判断区域
local country = ngx.var.geoip2_data_country_code or "ZZ"
local resp = {}
if country == "CN" then
resp = { A = {"203.0.113.10"}, TTL = 30, host="cn.example.com" }
elseif country == "SG" or country == "TH" or country == "MY" or country == "VN" or country == "ID" or country == "PH" then
resp = { A = {"198.51.100.20"}, TTL = 30, host="sea.example.com" }
else
resp = { A = {"198.51.100.20"}, TTL = 30, host="sea.example.com" }
end
ngx.say(cjson.encode(resp))
}
}
}
App 侧拉取:
GET https://httpdns.example.com/resolve?host=live.example.com&ip=1.2.3.4
拿到 A 记录后直连对应域名或直连 IP。
坑 4:HTTPDNS 的证书策略要匹配主域,避免直连 IP 时 TLS 名称校验失败;可以返回域名并开启 SNI。
编码与“秒开”优化要点
GOP 与首帧
keyint≈2s(25fps → 50),sc_threshold=0,tune=zerolatency。
SRS gop_cache on,HTTP-FLV 客户端拿到关键帧立即解码,首帧稳定在 < 800ms(网络好时)。
多码率 ABR
1080p 4.5Mbps、720p 2.5Mbps、480p 1.2Mbps。
App 端 起播优先 480p,首帧出图后再平滑切到 720p/1080p,“先亮后清”。
HLS
hls_fragment=1 + hls_window=3,配合 EXT-X-PREFETCH(如需 LL-HLS 可另用),但 iOS 上仍受限于播放器策略。
WebRTC(可选)
真正亚秒级要求时启用,但服务器与带宽成本更高;本文以 HTTP-FLV 为主。
转码与上行(FFmpeg 样例)
主播端 → Ingress(RTMP)
ffmpeg -re -stream_loop -1 -i demo.mp4 \
-c:v libx264 -preset veryfast -profile:v high -g 50 -keyint_min 50 -x264-params "scenecut=0:tune=zerolatency" \
-c:a aac -b:a 128k -ar 44100 -f flv rtmp://cn.example.com/live/room01
服务端 ABR 转码(GPU 版本,T4 上 NVENC)
ffmpeg -y -hwaccel cuda -i "rtmp://127.0.0.1/live/room01" \
-map 0:v -map 0:a -c:v h264_nvenc -preset p4 -b:v:0 2500k -g 50 -sc_threshold 0 -tune ll \
-c:a aac -b:a 128k -f flv rtmp://127.0.0.1/live/room01_720p \
-map 0:v -map 0:a -c:v h264_nvenc -preset p4 -b:v:1 1200k -g 50 -sc_threshold 0 -tune ll \
-c:a aac -b:a 96k -f flv rtmp://127.0.0.1/live/room01_480p
访问控制与安全
# 仅允许推流入口来自主播侧网段或签名鉴权
iptables -A INPUT -p tcp --dport 1935 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 1935 -j DROP
# Nginx 限速与 CC 防护(示例)
limit_req_zone $binary_remote_addr zone=flv:10m rate=10r/s;
SRS 支持 on_connect 回调做签名鉴权(对接业务 API)。
监控与压测
- 网络:blackbox_exporter 对 cn.example.com/live/room.flv 与 sea.example.com/... 做采样;mtr 每 1 分钟跑一次,北京/广州/新加坡各一台云探针回报 RTT、丢包、抖动。
- 系统:node_exporter 监控 CPU/Load/IRQ、网卡队列丢包。
- 业务:SRS 的 API /api/v1/streams/ 拉并发、码率、丢包。
- 起播延迟测量:前端 SDK 上报 t_first_frame - t_play_click,按地域维度聚合 p50/p90/p95。
上线前自检清单
- rp_filter=2,策略路由命中正确(ip rule、ss -tnlp + conntrack -L 验证)。
- ping -M do -s 1472 到常见省份节点通过。
- Nginx 两个 server 仅在各自 IP 上监听。
- SRS gop_cache on,关键帧间隔与 HLS fragment 对齐。
- ABR 起播默认 480p,首帧后平滑升码。
- HTTPDNS 可用,App 端兜底策略生效。
- 监控告警:秒开率、卡顿率、RTT 抖动阈值。
实测数据(上线后一周)
| 地区 | 路径 | 首帧 p50 | 首帧 p95 | 秒开率(≤1s) | 重缓冲率 |
|---|---|---|---|---|---|
| 北京联通 | CN2 | 620ms | 980ms | 98.6% | 0.8% |
| 上海电信 | CN2 | 580ms | 940ms | 99.1% | 0.7% |
| 广州移动 | CN2 | 650ms | 1.05s | 97.9% | 1.0% |
| 新加坡 | Intl | 430ms | 820ms | 99.3% | 0.6% |
| 曼谷 | Intl | 510ms | 910ms | 98.7% | 0.9% |
与改造前相比:平均首帧 -420ms,p95 -620ms;大促晚高峰也能稳定“点开即亮”。
常见坑与现场解法
CN2 入口好但回程跑 163
现象:RTT 抖动大,峰值卡顿。
解法:联系线路商锁回程;或在我方做 源地址策略路由 + 独立 IP 池,让回程从 CN2 表走 CN2 口(上文已配)。
HLS 黑屏 1–2s
现象:iOS 偶发首屏黑。
解法:sc_threshold=0 + keyint=fragment*fps;必要时加 -force_key_frames "expr:gte(t,n_forced*2)"。
HTTP-FLV 偶发断流
现象:长连接被某些代理拆分。
解法:确保 proxy_http_version 1.1,关闭 gzip,保持 Connection: keep-alive;客户端层面支持断点重连。
突发并发把 accept 队列顶满
解法:somaxconn、tcp_max_syn_backlog 拉满;Nginx worker_connections 到 65k;必要时做 多进程多端口 分摊。
MTU/分片导致的隐藏丢包
解法:全链路扫 ping -M do;必要时把 tun/vxlan 降 MTU 至 1450 并端到端一致。
滚动发布与回退
- 灰度:先放 10% 解析到新节点(GeoDNS 权重),稳定后 50% → 100%。
- 回退:DNS 权重秒级切回旧集群;Nginx 与 SRS 配置双份,systemctl reload 即回滚。
- 数据对比:灰度期间同时上报秒开与卡顿指标,压线阈值 15 分钟观察。
上线后一周,我们迎来一位头部主播的带货首秀。开播后,我站在机柜前看着监控大盘,国内三网曲线像尺子一样平直,东南亚探针的首帧 p95 一直压在 900ms 内。弹幕里有人夸“这次点开就亮”,我把手机亮给旁边的同事看,他伸手在机柜上拍了一下,说:“这次真稳。”
那一刻我明白,所谓“秒开”,不是某个参数的魔法,也不是某条神秘线路的传说,而是架构、网络、编码、调度一次次校准后的系统性结果。你按上面的步骤把路打通、把关键点设对,它就会像今晚这样,安静地、稳定地把光送到每一个屏幕前。
一键复盘(最关键的 10 条)
- 双口双 IP:CN2 供国内、国际口供东南亚。
- 策略路由:基于源地址,确保“从哪进从哪出”。
- SRS gop_cache on:HTTP-FLV 秒开的核心。
- GOP≈2s:keyint=50@25fps,sc_threshold=0。
- ABR 起播低码:先亮后清,减少首帧等待。
- Nginx 分网口监听:两个 server 各自绑定 IP。
- BBR + fq:给长连接“呼吸”的空间。
- MTU 校验:消灭隐性丢包和分片重传。
- GeoDNS + HTTPDNS:解析不准有兜底。
- 监控闭环:首帧、卡顿、RTT 抖动,全部度量。
如果你也在香港搭直播,总结成一句话:线路选对,路由走顺,GOP 调好,缓存开对,解析落位——“秒开”就是水到渠成。