作者:OpenVidu
译自:https://openvidu.medium.com/the-architecture-of-scale-how-to-scale-video-conferencing-from-a-single-server-to-a-3cac46085163
引言:成功的陷阱
发布周往往感觉一切都很完美。你交付了一个 MVP,用户快速加入通话,早期反馈也很积极。然后增长来得比预期更快。
一位客户安排了一次全公司会议。数百人加入。你最棒的演示变成了你的第一次重大事故:CPU 飙升、带宽饱和、音频卡顿、视频冻结。产品没有失败不是因为团队缺乏人才。它失败了,是因为实时媒体的扩展方式与传统的 Web 应用完全不同。
无状态的 API 通常可以通过增加副本和负载均衡器来吸收流量。但视频会议不行。每个参与者都维持着一条长连接的、有状态的信道,而且每个音频和视频数据包都必须以极低延迟进行加密和路由。数据库查询可以承受 200 毫秒的延迟,但视频通话却不行,用户立刻就会注意到抖动、间隙和数据包丢失。
这就是扩展视频会议是一个真正困难问题的原因。这不是一个通过添加内存就能解决的硬件问题。你需要一种能够伴随你成长的架构。本文将引导你走过一个三阶段路线图:
- 单节点:几乎所有成功产品起步的地方。
- 水平弹性媒体层:如何扩展系统中实际处理通话的部分。
- 高可用控制平面:如何防止单点故障导致整个平台宕机。
在此过程中,你还将学习如何构建一个在饱和发生前就能做出反应的自适应扩展循环,以及如何通过准入规则在流量突增时保护通话质量。
第一阶段:单节点(单体阶段)
大多数成功的平台都从一台机器起步。在单节点部署中,一台服务器同时运行信令、媒体处理、持久化和 API 逻辑。对许多团队来说,这是正确的选择,它在你还在验证产品是否有价值时最大化学习速度并最小化运维负担。

为什么单节点早期有效
运维简单在验证产品阶段确实很有价值。单节点部署为你提供:
- 运维复杂度低。 一台主机,一条部署路径,一个故障域。
- 调试速度快。 日志、指标和追踪都在同一处。关联分析很简单。
- 无服务间延迟。 信令、编排和媒体协调共享同一环境。
- 成本下限低。 在你还在测试需求时没有闲置的集群开销。
在这个阶段,你在为产品探索优化,而不是为最大容量优化。这是一个好的权衡。
无法忽视的天花板
单节点模型总会遇到硬性上限,而第一个崩溃的通常是 CPU。每条活跃流都会迫使服务器做实时工作:
- 为每个参与者建立和拆除加密会话
- 持续解密入站数据包并重新加密出站数据包
- 实时在参与者之间路由媒体
- 运行拥塞控制和自适应码率算法
- 可选地录制或转码
随着房间规模扩大,这些操作叠加。CPU 在峰值流量期间非线性攀升——不是逐渐的。不知不觉间,一场大型通话就让什么都没剩给其他人了。
网络吞吐量是第二道墙。网卡即使在 CPU 看起来健康的情况下也会饱和。而爆炸半径是第三道:一台机器托管每个会话,因此一次故障意味着所有客户同时受影响。
Mesh vs SFU:为什么拓扑结构决定了你的上限
通话拓扑结构对你的上限影响比大多数人想象的要大。
在纯点对点 mesh 中,每个参与者直接向其他每个参与者发送媒体。连接数量大约以 O(N²) 的速度增长对两个人或三个人来说没问题,对十个人来说完全不切实际。客户端上传带宽爆炸,客户端 CPU 饱和,用户体验在你服务器还没反应过来之前就已经崩溃了。
SFU(选择性转发单元) 将这项工作集中到服务器上。每个客户端发送一条上行流;SFU 选择性地将其转发给其他参与者。客户端保持精简,基础设施保持在控制之下,了解什么被发送到哪里。
什么时候一台节点不够用了?
不要等到事故发生才回答这个问题。在发布前就设置客观阈值:
- 峰值时段 CPU 高于 70%
- 持续出站流量接近网卡实际上限
- 加入高峰时数据包丢失率上升
- 负载下会话建立时间增加
当这些信号持续出现时,用更大的机器解决问题只是延迟了真正的对话。你需要水平扩展。
第二阶段:水平可扩展性与弹性(媒体层)
此时,更大的机器成本更高,效果更差。你真正需要的是一种角色分离的架构,其中媒体执行和编排独立扩展。

拆分如下:
- 编排器/支撑集群: 处理入口处的负载均衡、全局协调、共享状态和数据库支持的元数据。
- 媒体节点: 房间实际所在的机器:数据包路由、加密和转发的机器。
你有时会看到这被描述为主-从模型。大致准确,但角色之间的区别比标签更重要。
正确的角色分离:编排器 vs 媒体节点
编排器不处理单个媒体数据包。它决定新会话应该落在哪里,保持全局集群状态一致,并处理平台级协调。把它想象成交通管制员,而不是高速公路本身。
媒体节点一旦被分配到一个房间,就成为该会话的权威运行时。它在通话进行时路由数据包、跟踪参与者媒体状态、做自适应决策,并处理房间级逻辑。
实际上:
- 编排器 拥有准入、放置和跨集群协调权。
- 媒体节点 拥有房间:实时媒体流、局部状态、拥塞响应。
- 共享系统(Redis、数据库、服务发现)维护两个平面都读取的全局视图。
结果是更清晰的扩展单元:通过添加节点来增加媒体容量,而不必触碰编排逻辑。
水平扩展实际上解决了什么
添加媒体节点的作用不仅仅是增加余量:
- 容量增长: 更多并发参与者和房间。
- 故障隔离: 单个媒体节点故障只影响该节点上的房间,而不是整个平台,爆炸半径大幅缩小。
- 成本控制: 根据流量模式调整节点池大小,在需求下降时收缩。
- 性能局部性: 将工作节点放置在它们服务的用户附近。
流量增长,你的控制逻辑保持不变。
弹性:动态增减节点
没有弹性的水平扩展只是一个更大的固定集群,在安静时段仍然浪费钱,在高峰时仍然不够。真实流量是突发性的。你的容量需要跟随它。
自适应扩展最好由实际反映媒体负载的信号驱动,而不仅仅是 CPU 百分比:
- Worker CPU 使用率
- 网络吞吐量(入站和出站)
- 活跃发布者和订阅者数量
- 媒体数据包处理队列压力
- 新房间准入延迟
一个健壮的自适应扩展循环大致如下:
- 持续收集 worker 健康状况和负载指标。
- 计算每个节点的余量和聚合集群容量。
- 在饱和发生前触发扩展,而不是响应饱和。
- 在路由表中注册新 worker,以便它们立即开始接收流量。
- 将新房间引导到最健康、余量最大的节点。
关于弹性的关键洞察:在满载前反应,而不是之后。 当你的指标显示饱和时,用户已经在经历质量降级了。主动扩展(在 65–70% 容量时触发)让你走在需求前面。
准入控制比节点数量更重要
这是经常让团队惊讶的事情:集群中的节点数量没有你如何仔细地将房间分配给节点重要。
随机放置会产生热点。一个以 75% CPU 运行特大型房间的节点,比一个以 55% CPU 运行较小通话的节点更糟糕——即使两者都不是技术上”满的”。如果你的放置逻辑不考虑实际负载分布,你会在达到原始容量限制之前很久就遇到质量问题。
要落实的实际准入规则:
- 每个节点的发布者、订阅者和总比特率的硬上限。
- 为突发留出余量的软阈值,而不会因每个峰值而触发扩展。
- 房间放置的区域和延迟亲和性,使参与者被路由到地理位置最近的节点。
关于准入控制的关键洞察:节点数量保护你不会耗尽基础设施。准入规则保护你已有基础设施内的通话质量。你两者都需要。
隐藏的难点:缩容
启动新的媒体节点是简单的。安全地移除它们是大多数团队低估的部分。
如果在活跃房间正在其上运行时终止一个节点,用户会看到通话中断或重新协商风暴,所有连接突然同时重新建立,这往往会导致部分失败。缩容不能是一条 kill 命令。它必须是一个工作流。
一个正确的优雅缩容策略如下:
- 将节点标记为排空中。
- 停止向其分配新房间。
- 让现有房间自然结束,或者如果排空超时被超过,则显式迁移会话。
- 仅在节点达到安全空状态后才终止它。
这需要编排逻辑、可配置超时和明确的故障处理。这往往是写在白板上的”弹性”和凌晨 3 点实际起作用的弹性之间的差距。
第三阶段:高可用(控制平面)
你可以运行 100 个媒体节点,但如果你的编排层是一个单机实例,仍然会像原型一样失败。
当编排下线时,新加入失败,放置停止,恢复成为一个手动过程。第二阶段加固了媒体层。第三阶段是确保系统的其余部分也能在故障中存活。因为在这个阶段,高可用意味着加固支撑集群,而不是添加更多媒体节点。

消除单点故障
支撑集群中的每个关键服务都需要副本。检查清单:
- 信令和控制服务:复制集群,而非单机进程。
- Redis(或等效状态存储):带故障转移的高可用拓扑。
- 主数据存储 :配置复制和自动故障转移。
- 负载均衡器 :跨可用区分布。
- 服务发现: 健康感知路由,而非单一注册表。
如果其中任何一个以单机实例运行,那就是你下一次宕机的等待。
法定人数:防止脑裂的规则
分布式控制系统需要一个明确的答案来回答一个问题:当对谁负责出现分歧时,谁赢?
答案是法定人数(quorum):在做出任何权威决策之前,必须有多数节点同意。大多数基于共识的系统需要 N/2 + 1 个节点:
- 3 个节点 → 法定人数为 2
- 4 个节点 → 法定人数为 3
- 5 个节点 → 法定人数为 3
这条规则的存在专门是为了防止脑裂,即网络分区导致两边都认为自己负责并开始做出冲突写入的场景。
在 4 节点控制集群中发生 2-2 网络分裂时,两边都没有法定人数。都不能做出权威决策。这不舒服,但这是正确的行为:系统保护一致性而非可用性,并防止两个 leader 发散状态。
许多团队选择 4 个节点作为跨可用区的务实平衡。其他人则偏好奇数规模集群(3 或 5)以获得更清晰的法定人数算术。正确的选择取决于你的一致性模型以及你的状态存储如何处理选举。
状态同步:保持会话真相一致
节点数量只是故事的一半。另一半是确保你的副本实际对正在发生的事情达成一致。
你的支撑集群需要同步:
- 房间元数据
- 参与者在场状态和角色分配
- 活跃房间的媒体节点分配
- Token 和授权上下文
- 录制状态和网络钩子传递状态
并非所有这些都需要同等程度的一致性强一致。对于必须唯一的决策(例如房间所有权)需要强一致性。对于遥测和分析,最终一致性就够了。明确你希望哪种数据类型获得哪种保证。
还有一件事值得从一开始构建:幂等操作。在分布式系统中,重试是常态——故障转移、超时、重试。如果你的操作不是幂等的,你会在最糟糕的时刻得到重复的副作用。
实时分布的挑战
到了第三阶段,你运行的是一个严肃的分布式实时系统。难题不再是”它能跑吗?“而是”当流量不可预测且节点发生故障时,它能保持稳定吗?”
两件事决定答案:你的放置决策有多智能,以及你能多好地观察媒体层内部实际发生的事情。
智能放置优于轮询
轮询负载均衡假设每个会话成本相同。在视频平台上,这几乎从来不是真的。
一个有 50 名参与者和高分辨率视频流的房间可能消耗 10 倍于一个有 5 名参与者和低分辨率通话的房间的 CPU 和带宽。如果你的放置逻辑不考虑这一点,少数重房间将饱和单个节点,而集群其余部分基本上闲置。
资源感知放置查看真正重要的信号:
- 当前 CPU 使用率及其近期趋势(它是否在攀升?)
- 当前入站和出站比特率
- 每个节点的活跃发布者数量
- 数据包丢失和抖动趋势
- 与加入参与者的地理邻近度
做得好,这保持质量稳定,并避免了一整类不是真正的容量问题——而是放置问题的故障。
媒体可观测性,而不仅仅是基础设施监控
CPU 和内存仪表板告诉你服务器是否健康。它们不会告诉你用户是否正在享受良好的通话。
为此,你需要媒体层信号:
- 客户端与媒体节点之间的 RTT(往返时间)
- 入站和出站流的抖动
- 双向数据包丢失率
- 重传率(拥塞的代理指标)
- 帧率下降和分辨率降级
- 音频连续性指标
当你将这些与信令事件和基础设施遥测相关联时,支持工单的性质会改变。从”音频在下午 2 点左右中断”变成你可以查看时间线,找到重传峰值,追踪到特定高负载节点,并准确知道发生了什么,以及为什么。
结论:按设计规模化,而非偶然
大多数视频平台故障不是工程故障。是架构故障,系统从未被设计成能处理它最终承受的负载。
这里的路线图很简单:从单节点快速起步,当需求增长时添加弹性媒体容量,当可用性变得不可或缺时加固你的控制平面。每个阶段都会引入真正的分布式系统复杂性,包括部署、资源耗尽、法定人数、状态一致性、可观测性等,但每个阶段也有一个明确的存在理由和一个明确的必要时刻。
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/yinshipin/67225.html