一、大规模会议架构的核心挑战
做WebRTC的人都知道,两个人视频通话和两千人视频会议,完全是两个世界。
我最早做视频会议时,天真地以为把单路流复制几份就行。结果呢?服务器CPU直接拉满,客户端卡成PPT。嗯,这里有个关键问题:大规模会议的本质,是媒体流的复制与分发效率。
说白了,你不可能让每个客户端都跟其他人建立P2P连接。N个参与者,全连接需要N*(N-1)/2条链路。100人就是4950条,服务器和客户端都扛不住。
所以,我们需要一个中间节点来集中处理媒体流。这就引出了两种经典架构:SFU 和 MCU。
核心结论:现代大规模会议几乎全部采用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架构,接下来就是选具体实现。目前最主流的两个开源方案:Janus 和 Mediasoup。
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 分层负载均衡
- 媒体层:SFU节点只负责转发,不做任何AI处理。
- AI层:独立的AI节点集群,专门跑模型推理。
- 调度层:根据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集群和动态伸缩层,一目了然。
七、总结
大规模会议架构,说白了就是三件事:选对SFU方案、分离AI负载、做好动态伸缩。
我个人更倾向于Mediasoup + 自研AI集群 + Kubernetes的组合。轻量、灵活、可控。Janus虽然功能多,但太重了,适合需要快速集成的场景。
最后提醒一句:永远不要相信“上线前测试没问题”这句话。大规模并发下,网络抖动、服务器负载、客户端差异,任何一个环节都可能出问题。做好监控和降级预案,比什么都重要。
我的经验:每次上线前,我都会做一次“混沌工程”测试——随机杀掉一个SFU Worker,看看系统能不能自动恢复。这个习惯帮我避免了好几次线上事故。