11、大规模会议架构:SFU与MCU的选型、媒体流路由策略、服务端转码与合流
说到大规模会议,很多人的第一反应是“堆机器”。但说实话,堆机器解决不了架构问题。我见过不少团队,服务器配得挺豪华,结果200人一进房间,CPU直接拉满,画面卡成PPT。问题出在哪?出在选型上——SFU还是MCU,这步棋走错了,后面再怎么优化都白搭。
这一章,我就把大规模会议架构的核心拆开揉碎。咱们从选型开始,聊到路由策略,再聊到服务端转码和合流。嗯,都是我在线上真实项目中踩过的坑,希望能帮你少走弯路。
11.1 SFU vs MCU:选型背后的逻辑
先看一张图,帮你快速建立整体认知:
这张图其实已经说得很清楚了。但我还是想补充几句:
SFU(Selective Forwarding Unit),说白了就是个“快递员”。它不拆包裹,只负责把每个人的音视频包转发给其他人。客户端收到N路流,自己解码、自己渲染。好处是服务器压力小,坏处是客户端压力大——你想想看,500人的会,每个客户端要同时解码499路视频,普通笔记本根本扛不住。
MCU(Multipoint Control Unit),就是个“中央厨房”。服务端把所有视频流解码、合流成一路,再编码推给客户端。客户端只收一路流,压力极小。但服务端要同时解码N路、合流、再编码,CPU和内存消耗巨大。我在项目中遇到过,用MCU跑30人会议,一台8核服务器CPU直接飙到90%。
核心结论:
- 50人以上大规模会议 → 选SFU
- 20人以下精品互动会议 → 选MCU
- 20-50人区间 → 混合架构(SFU+MCU组合)
11.2 媒体流路由策略
选定了SFU,不代表万事大吉。路由策略才是真正考验功力的地方。我见过最典型的翻车案例:所有客户端都请求最高分辨率,SFU照单全收,结果网络拥塞,全员卡顿。
路由策略的核心就一句话:按需分配,动态调整。
11.2.1 Simulcast(联播)
Simulcast 是 SFU 场景下最常用的策略。发送端同时编码多路不同分辨率的流(比如 720p、360p、180p),SFU 根据接收端的能力和网络状况,选择转发哪一路。
我个人习惯这样配置:
// 发送端 Simulcast 配置示例
const encodings = [
{ rid: 'h', maxBitrate: 2000000, maxFramerate: 30, scaleResolutionDownBy: 1.0 }, // 720p
{ rid: 'm', maxBitrate: 500000, maxFramerate: 24, scaleResolutionDownBy: 2.0 }, // 360p
{ rid: 'l', maxBitrate: 150000, maxFramerate: 15, scaleResolutionDownBy: 4.0 }, // 180p
];
// 接收端订阅逻辑
// 当接收端屏幕较大且带宽充足 → 订阅 rid: 'h'
// 当接收端为手机且带宽一般 → 订阅 rid: 'm'
// 当接收端网络较差 → 订阅 rid: 'l'
避坑指南:我曾经在项目中把 maxBitrate 设得太高,结果移动端网络波动时,SFU 降级到低分辨率流,但码率依然偏高,导致频繁卡顿。后来我加了一个动态码率阈值:当丢包率超过5%时,强制切换到 rid: 'l',效果立竿见影。
11.2.2 SVC(可伸缩视频编码)
SVC 是另一种思路。它把视频编码成多层:基础层(必选)+ 增强层(可选)。SFU 可以只转发基础层,或者基础层+部分增强层。相比 Simulcast,SVC 的带宽利用率更高,但编码复杂度也更高。
说实话,目前 WebRTC 对 SVC 的支持还不够成熟。我在生产环境中更倾向于 Simulcast,因为它更稳定,调试起来也更方便。
11.2.3 动态订阅与带宽估计
路由策略不能是静态的。客户端网络状况随时在变,SFU 必须能动态调整。我常用的做法是:
- 接收端上报:客户端定期上报丢包率、RTT、可用带宽。
- SFU 决策:根据上报数据,决定是否降级或升级订阅的流。
- 平滑切换:切换时做短暂缓冲,避免画面闪烁。
注意:不要频繁切换!我曾经见过一个项目,每秒钟切换一次分辨率,结果画面一直在跳,用户体验极差。建议切换间隔至少3秒,并且要有滞回区间(比如丢包率从5%降到3%才升级,而不是降到4.9%就升级)。
11.3 服务端转码与合流
虽然 SFU 不转码,但在某些场景下,服务端转码是绕不开的。比如:
- 客户端不支持某种编码格式(比如老版本浏览器只支持 H.264,而新客户端用了 VP9)
- 需要录制合流后的视频(比如会议回放)
- 需要做内容审核(比如实时检测画面中的敏感信息)
11.3.1 转码的代价
转码是 CPU 密集型操作。我做过一个测试:
| 操作 | CPU 消耗(单路 720p) | 延迟增加 |
|---|---|---|
| 纯转发(SFU) | ~2% | <1ms |
| 转码(H.264 → VP9) | ~35% | ~30ms |
| 合流(4路 → 1路) | ~60% | ~50ms |
看到没?转码和合流的代价非常大。所以我的原则是:能不转就不转,能不合就不合。如果非要转码,建议用硬件编码器(如 Intel QSV、NVIDIA NVENC),能大幅降低 CPU 消耗。
11.3.2 合流的布局策略
合流时,布局是个大学问。常见的布局有:
- 演讲者模式:主画面占 80%,其他参与者以小窗排列在底部。
- 画廊模式:所有参与者均分画面,适合 4-9 人会议。
- 自定义布局:根据业务需求动态调整,比如把某个重要参会者放大。
我在项目中实现过一个动态布局引擎:当发言者变化时,自动把当前发言者切换到主画面。嗯,这里要注意,切换时要做平滑过渡,否则画面会突然跳动,很影响体验。
// 合流布局伪代码
function composeLayout(participants, activeSpeaker) {
const layout = [];
if (activeSpeaker) {
// 演讲者模式
layout.push({ id: activeSpeaker.id, x: 0, y: 0, w: 0.8, h: 1.0 });
const others = participants.filter(p => p.id !== activeSpeaker.id);
others.forEach((p, i) => {
layout.push({ id: p.id, x: 0.8, y: i * 0.2, w: 0.2, h: 0.2 });
});
} else {
// 画廊模式
const cols = Math.ceil(Math.sqrt(participants.length));
const rows = Math.ceil(participants.length / cols);
participants.forEach((p, i) => {
const col = i % cols;
const row = Math.floor(i / cols);
layout.push({ id: p.id, x: col / cols, y: row / rows, w: 1/cols, h: 1/rows });
});
}
return layout;
}
关键点:合流后的视频编码参数要单独配置。不要直接用某个参与者的编码参数,因为合流后的画面分辨率、帧率、码率都需要重新计算。我一般会设置一个独立的编码器配置,比如合流后输出 1080p 30fps,码率控制在 4Mbps 以内。
11.4 实战中的架构选择
最后,我分享一个我在项目中实际用过的架构:
- 核心 SFU:使用 mediasoup,支持 Simulcast,动态订阅。
- 边缘转码节点:在靠近客户端的边缘节点部署转码服务,只对少数不兼容的客户端做转码。
- 合流录制服务:单独部署一台服务器,专门负责合流和录制,不参与实时转发。
这个架构的好处是:核心转发路径没有转码开销,延迟极低。转码和合流被隔离到独立节点,即使出问题也不会影响主会议。
嗯,差不多就这些。大规模会议架构没有银弹,关键是根据你的场景做取舍。选 SFU 还是 MCU,用 Simulcast 还是 SVC,要不要转码——每个决策背后都是成本和体验的权衡。希望这一章能帮你理清思路,少踩一些我踩过的坑。