一、大规模会议架构的核心挑战

做WebRTC的人都知道,两个人视频通话和两千人视频会议,完全是两个世界。

我最早做视频会议时,天真地以为把单路流复制几份就行。结果呢?服务器CPU直接拉满,客户端卡成PPT。嗯,这里有个关键问题:大规模会议的本质,是媒体流的复制与分发效率

说白了,你不可能让每个客户端都跟其他人建立P2P连接。N个参与者,全连接需要N*(N-1)/2条链路。100人就是4950条,服务器和客户端都扛不住。

所以,我们需要一个中间节点来集中处理媒体流。这就引出了两种经典架构:SFUMCU

核心结论:现代大规模会议几乎全部采用SFU架构。MCU只在极少数特殊场景下使用。

二、SFU vs MCU:选型对比

2.1 MCU(多点控制单元)

MCU的思路很简单:把所有参与者的视频流拉到服务器,合成一路画面,再推给所有人。

优点:客户端压力小,只需要解码一路流。

缺点:服务器压力巨大,需要实时转码、混流、合图。延迟高,成本高。

我曾经在2018年帮某教育公司搭过MCU方案。一台8核服务器,撑死支持30路720p合成。客户说要做500人课堂,我直接建议换方案。

避坑指南:MCU的转码开销是O(N)的,但合流后的分辨率、帧率、码率是固定的。如果你需要每个客户端看到不同布局(比如老师看学生列表,学生只看老师),MCU就非常笨重。

2.2 SFU(选择性转发单元)

SFU不做转码,只做转发。每个客户端上传一路流,SFU根据订阅关系,把流复制给需要的接收方。

优点:服务器开销小(纯转发),延迟低,支持灵活订阅。

缺点:客户端需要解码多路流,对上行带宽要求高。

我个人的习惯是:只要客户端设备不是十年前的老古董,一律用SFU。现在的手机、笔记本解码4路1080p完全没问题。

对比维度 SFU MCU
服务器CPU开销 低(纯转发) 高(转码+混流)
延迟 低(毫秒级) 高(百毫秒级)
客户端解码路数 多路(N-1) 1路
灵活性 高(按需订阅) 低(固定布局)
典型场景 视频会议、直播、互动课堂 小型会议、硬件终端

三、Janus vs Mediasoup:部署实战

选好了SFU架构,接下来就是选具体实现。目前最主流的两个开源方案:JanusMediasoup

3.1 Janus

Janus是一个通用的WebRTC服务器,插件化架构。它本身是一个网关,可以对接各种业务逻辑。

优点:功能丰富,文档齐全,社区活跃。支持录音、录像、转码、SIP网关等。

缺点:架构较重,每个插件独立进程,资源开销大。配置复杂。

我记得有一次部署Janus,光配置nginx转发WebSocket就折腾了半天。它的插件间通信用的是Unix Socket,调试起来比较麻烦。

# Janus 典型部署结构
# 使用 videoroom 插件实现多人会议

# janus.plugin.videoroom.jcfg
room-123: {
  description = "大规模会议测试房间",
  publishers = 50,          # 最大发布者数
  bitrate = 1024000,        # 默认码率 1Mbps
  fir_freq = 10,            # 关键帧请求间隔
  audiocodec = "opus",
  videocodec = "h264",
  record = false
}

我的建议:如果你需要快速集成多种功能(比如直播推流、录制、SIP互通),Janus是更好的选择。但如果你只需要纯粹的多人会议,Mediasoup更轻量。

3.2 Mediasoup

Mediasoup是一个专注于SFU的库,不是完整的服务器。它只做一件事:媒体流的接收和转发。

优点:极致轻量,性能极高。基于C++实现核心,Node.js/Python做控制层。支持Worker多进程。

缺点:功能单一,没有内置录制、转码等功能。需要自己实现信令和业务逻辑。

我最近的一个项目,用Mediasoup搭建了2000人同时在线的大课堂。单台8核服务器,轻松支撑500路并发。你想想看,这效率比Janus高了多少?

// Mediasoup 核心代码示例
// 创建 Router 和 Transport

const mediasoup = require('mediasoup');

async function createWorker() {
  const worker = await mediasoup.createWorker({
    logLevel: 'warn',
    rtcMinPort: 40000,
    rtcMaxPort: 49999
  });
  
  const router = await worker.createRouter({
    mediaCodecs: [
      {
        kind: 'audio',
        mimeType: 'audio/opus',
        clockRate: 48000,
        channels: 2
      },
      {
        kind: 'video',
        mimeType: 'video/VP8',
        clockRate: 90000
      }
    ]
  });
  
  return { worker, router };
}

避坑指南:Mediasoup的端口范围需要提前规划。默认是40000-49999,如果服务器有防火墙,记得开放这些UDP端口。我曾经因为忘记开端口,排查了整整一个下午。

四、AI负载均衡:让智能不拖累性能

大规模会议里,AI功能(语音识别、人脸检测、背景虚化)是CPU杀手。你不能让每个SFU节点都跑AI模型,那样服务器会直接崩溃。

我的做法是:把AI处理从媒体转发链路中剥离出来

4.1 分层负载均衡

  1. 媒体层:SFU节点只负责转发,不做任何AI处理。
  2. AI层:独立的AI节点集群,专门跑模型推理。
  3. 调度层:根据AI节点的负载,动态分配任务。

举个例子:语音识别。SFU收到音频流后,通过内部消息队列(比如RabbitMQ)把音频片段发给AI节点。AI节点处理完,把结果写回Redis。客户端通过WebSocket拉取结果。

// AI负载均衡伪代码
// 调度器根据AI节点CPU使用率分配任务

function assignAITask(audioChunk) {
  const nodes = getAIClusterNodes();
  const sorted = nodes.sort((a, b) => a.cpuUsage - b.cpuUsage);
  const target = sorted[0]; // 选CPU最低的节点
  
  if (target.cpuUsage > 80) {
    // 所有AI节点都忙,降级处理
    return fallbackToNoAI();
  }
  
  return target.process(audioChunk);
}

关键点:AI负载均衡的核心是任务粒度。不要把整个会议的AI处理都丢给一个节点。按用户粒度拆分,每个用户的音频/视频独立处理。这样即使某个AI节点挂了,也只影响个别用户。

五、动态伸缩策略:从100人到10000人

会议规模是动态的。早上可能只有10个人开会,下午突然涌入2000人。你不能一直开着2000人的资源,那太浪费了。

我习惯用Kubernetes + HPA来做自动伸缩。但要注意,WebRTC的媒体流是有状态的,不能像无状态服务那样随便扩缩。

5.1 水平扩展策略

指标 触发条件 动作
CPU使用率 > 70% 持续30秒 增加1个SFU Worker
内存使用率 > 80% 持续1分钟 增加1个SFU节点
活跃连接数 > 500 每节点 触发新节点创建
AI任务队列长度 > 1000 待处理 增加AI节点

5.2 缩容策略

缩容比扩容更危险。你不能直接把有用户的节点干掉。

我的做法是:先标记节点为“不可调度”,等待所有用户离开后,再回收资源

# Kubernetes HPA 配置示例
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: sfu-worker-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: sfu-worker
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300  # 等待5分钟再缩容
      policies:
      - type: Pods
        value: 1
        periodSeconds: 60

避坑指南:缩容时一定要设置stabilizationWindowSeconds。我曾经没设这个参数,结果用户刚加入会议,节点就被缩掉了,导致所有人断线重连。那场面,简直灾难。

六、架构总览图

下面这张图展示了大规模会议的整体架构。从客户端到SFU,再到AI集群和动态伸缩层,一目了然。

大规模会议架构总览 Web客户端 移动客户端 桌面客户端 SFU 媒体转发层(Janus / Mediasoup) 多Worker进程 | 按房间隔离 | 纯UDP转发 AI 推理集群 语音识别 | 人脸检测 | 背景虚化 调度 & 负载均衡 Kubernetes HPA | 任务队列 | 降级策略 动态伸缩策略:CPU > 70% → 扩容 | 稳定5分钟 → 缩容 客户端 SFU AI 调度

七、总结

大规模会议架构,说白了就是三件事:选对SFU方案、分离AI负载、做好动态伸缩

我个人更倾向于Mediasoup + 自研AI集群 + Kubernetes的组合。轻量、灵活、可控。Janus虽然功能多,但太重了,适合需要快速集成的场景。

最后提醒一句:永远不要相信“上线前测试没问题”这句话。大规模并发下,网络抖动、服务器负载、客户端差异,任何一个环节都可能出问题。做好监控和降级预案,比什么都重要。

我的经验:每次上线前,我都会做一次“混沌工程”测试——随机杀掉一个SFU Worker,看看系统能不能自动恢复。这个习惯帮我避免了好几次线上事故。


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