11、大规模会议架构:SFU与MCU的选型、媒体流路由策略、服务端转码与合流

说到大规模会议,很多人的第一反应是“堆机器”。但说实话,堆机器解决不了架构问题。我见过不少团队,服务器配得挺豪华,结果200人一进房间,CPU直接拉满,画面卡成PPT。问题出在哪?出在选型上——SFU还是MCU,这步棋走错了,后面再怎么优化都白搭。

这一章,我就把大规模会议架构的核心拆开揉碎。咱们从选型开始,聊到路由策略,再聊到服务端转码和合流。嗯,都是我在线上真实项目中踩过的坑,希望能帮你少走弯路。

11.1 SFU vs MCU:选型背后的逻辑

先看一张图,帮你快速建立整体认知:

大规模会议架构核心决策树 会议规模 & 需求 SFU(选择性转发) MCU(混合合流) SFU 特点 • 只转发,不解码 • 客户端负责解码渲染 • 带宽消耗 = N × 上行 • 适合 50-500 人会议 MCU 特点 • 服务端解码+合流 • 客户端只收一路流 • 带宽消耗 = 1 路合流 • 适合 5-20 人精品会议 结论:大规模选 SFU,小规模高互动选 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 必须能动态调整。我常用的做法是:

  1. 接收端上报:客户端定期上报丢包率、RTT、可用带宽。
  2. SFU 决策:根据上报数据,决定是否降级或升级订阅的流。
  3. 平滑切换:切换时做短暂缓冲,避免画面闪烁。

注意:不要频繁切换!我曾经见过一个项目,每秒钟切换一次分辨率,结果画面一直在跳,用户体验极差。建议切换间隔至少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,要不要转码——每个决策背后都是成本和体验的权衡。希望这一章能帮你理清思路,少踩一些我踩过的坑。


公众号:蓝海资料掘金营,微信 deep3321