生产环境部署:负载均衡、监控告警、大规模并发优化
说实话,很多团队做 WebRTC 的时候,Demo 跑得飞起,一上生产就崩。我见过最夸张的一次,线上 200 路并发,SFU 直接 OOM,整个服务挂了半小时。嗯,今天我们就聊聊怎么把 WebRTC 服务真正扛住生产压力。
一、架构层面的负载均衡
WebRTC 的负载均衡跟普通 HTTP 不一样。你想想看,HTTP 请求是短连接,做完就完事。但 WebRTC 是长连接,一个用户可能连着好几个小时。所以负载均衡的策略要专门设计。
1. 信令层的负载均衡
信令层说白了就是 WebSocket 或者 HTTP 请求。这部分用 Nginx 做反向代理就行,简单粗暴。我个人习惯用 ip_hash 策略,保证同一个用户的信令请求落到同一台机器上。
upstream signaling_servers {
ip_hash;
server 10.0.1.1:8080 weight=5;
server 10.0.1.2:8080 weight=5;
server 10.0.1.3:8080 weight=3;
keepalive 64;
}
server {
listen 443 ssl;
location /ws {
proxy_pass http://signaling_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
2. 媒体层的负载均衡
媒体层才是大头。WebRTC 的媒体流走的是 UDP,Nginx 管不了。这里我推荐两种方案:
- DNS SRV 记录:客户端通过 DNS 查询获取可用的 SFU 节点列表,然后自己选一个连。优点是简单,缺点是客户端要改代码。
- 专用调度器:部署一个独立的调度服务,负责分配 SFU 节点。调度器根据各节点的 CPU、内存、连接数来做决策。
我在项目中用的是第二种。调度器会维护一个节点状态表,每 5 秒更新一次。
// 调度器核心逻辑(伪代码)
function assignSFU(userId, roomId) {
let nodes = getAliveNodes();
// 优先选择同一房间用户所在的节点
let roomNode = findNodeByRoom(roomId);
if (roomNode && roomNode.load < 0.8) {
return roomNode;
}
// 否则选负载最低的节点
nodes.sort((a, b) => a.load - b.load);
return nodes[0];
}
二、监控告警体系
没有监控的生产环境,就像闭着眼睛开车。我个人习惯把监控分成三个层次:
1. 基础设施监控
| 指标 | 告警阈值 | 说明 |
|---|---|---|
| CPU 使用率 | > 80% 持续 5 分钟 | 编码解码很吃 CPU |
| 内存使用率 | > 85% | 每个 PeerConnection 约 50-100MB |
| 网络带宽 | > 70% 链路带宽 | 注意出站带宽,SFU 转发会放大流量 |
| UDP 丢包率 | > 5% | 直接影响音视频质量 |
2. 业务层监控
业务指标才是真正反映用户体验的。我建议至少采集这些:
- 并发连接数:当前活跃的 PeerConnection 数量
- 房间数:同时进行的会议数量
- 建连成功率:ICE 连接成功的比例,低于 95% 要报警
- 平均建连时间:从发起呼叫到媒体流到达的时间,超过 5 秒算异常
- 音视频码率:实时码率如果低于预期,说明网络有问题
核心指标:我个人最看重的是「建连成功率」和「平均建连时间」。这两个指标直接反映了用户能不能正常通话。有一次线上告警,建连成功率掉到 60%,排查发现是 STUN 服务器挂了。
3. 告警策略
告警不是越多越好。我曾经见过一个团队,一天收到 2000 条告警,最后没人看了。我的原则是:
- 分级告警:P0 级(服务不可用)直接电话+短信,P1 级(指标异常)发企业微信,P2 级(趋势预警)发邮件
- 抑制重复:同一个告警 30 分钟内不重复发送
- 关联分析:比如 CPU 高+丢包率高,可能是编码器出问题了,不要发两条独立的告警
三、大规模并发优化
说到大规模并发,很多人第一反应是加机器。但加机器解决不了所有问题。你想想看,如果每个 PeerConnection 都浪费 10% 的资源,10000 路并发就是 1000 路的浪费。
1. 编码器优化
编码是 WebRTC 最吃 CPU 的环节。我建议:
- 硬件编码优先:支持 Intel QSV、NVIDIA NVENC 的机器优先用硬件编码,能省 70% 的 CPU
- 动态码率调整:根据网络状况动态调整编码参数,不要固定码率
- SVC 分层编码:如果客户端支持,用 SVC 代替 Simulcast,能减少 SFU 的转码压力
// 动态码率调整示例
function onNetworkQuality(quality) {
let encoder = getVideoEncoder();
switch(quality) {
case 'good':
encoder.setBitrate(2000); // 2Mbps
encoder.setFrameRate(30);
break;
case 'fair':
encoder.setBitrate(800); // 800Kbps
encoder.setFrameRate(24);
break;
case 'poor':
encoder.setBitrate(300); // 300Kbps
encoder.setFrameRate(15);
break;
}
}
2. 连接复用
每个 PeerConnection 都会占用一个 UDP 端口和一定的内存。如果同一个用户同时加入多个房间,可以考虑复用连接。我在项目中做过一个优化:
- 同一个用户的所有媒体流走同一个 PeerConnection
- 通过 SSRC 区分不同的流
- 这样能减少 40% 的连接数
3. 内存管理
WebRTC 的内存泄漏是个老问题。我建议:
- 主动释放:用户离开房间后,立即关闭 PeerConnection,不要等 GC
- 内存池:对于频繁创建销毁的对象(如 RTP 包),用对象池复用
- 限制缓存:NACK 缓存、Jitter Buffer 的缓存大小要设上限
4. 带宽管理
带宽是稀缺资源。一个 1080p 的视频流大约需要 4Mbps,1000 路并发就是 4Gbps。我建议:
- 限制上行带宽:每个发布者限制最大码率,防止个别用户占用过多带宽
- 选择性转发:SFU 只转发用户真正需要的流,比如用户只看大屏,就不转发小屏的流
- 动态降级:当带宽不足时,自动降低分辨率或帧率
四、整体架构图
下面这张图展示了生产环境的完整部署架构。我把它分成三层:接入层、调度层、媒体层。
这张图里,接入层负责处理信令和 NAT 穿透,调度层负责分配 SFU 节点,媒体层负责实际的音视频转发。监控层贯穿整个架构,采集所有节点的指标。
好了,生产环境部署的核心要点就这些。负载均衡解决的是流量分发问题,监控告警解决的是可观测性问题,大规模并发优化解决的是资源效率问题。这三者缺一不可。