火山引擎 RTC 全球化架构设计

2,551 阅读15分钟

1. 为什么 RTC 要做全球化

RTC(Real Time Communication)是音视频基础设施,它已经融入了大家生活的方方面面:工作中,我们组织视频会议,即使团队成员身处异国,也能保证项目推进;休息时,我们打开抖音,看主播直播连麦;来一局游戏时,我们打开小队语音,大杀四方;学习时,我们相聚线上互动课堂,知识传播不再受距离的桎梏。RTC 拉近了大家的距离,丰富了大家的生活。

在这些场景里,我们最不能忍受的是什么?是延迟!想象一下,开会或者主播连麦时,一个人讲完话,其他人隔 10 秒才能做出反应,这几乎是完全不能接受的体验。

那么什么样的延迟才是好的体验呢?根据 ITU-T G.114 的建议:延时低于 400ms 的通话体验是可接受的,低于 200ms 是令人愉悦的。

图片

ITU-T G.114

在没有全球化的情况下,RTC 需要在公共互联网上进行长距离、跨国的网络传输,有大量因素会影响到通话质量:

  1. 距离越长,中间经过的路由节点越多,整体故障概率越大(软硬件问题,带宽问题,DDoS 等)
  1. 跨运营商网络问题
  1. 跨国网络问题
  1. ......

以纽约到深圳的数据为例:单向延迟最差的时候超过 1s,日常有 20% 左右的丢包。通过在纽约和深圳的设备进行实验,能看到通话建立成功的概率只有 30%,音视频延迟、卡顿非常严重。

RTC 做全球化的目的就是:降低延时,提供愉悦的 RTC 体验 。

2. 全球化架构分解

在对 RTC 进行全球化架构设计之前,我们需要先了解下 RTC 的业务逻辑。RTC 的所有应用场景都可以简化成一次通话。我将通话的流程做了简化,它包含下面的步骤:

图片

  1. 给信令服务器发送加入房间请求(同一个房间内的用户才能互相通话)
  1. 给信令服务器发送发布、订阅请求
  1. 信令服务器返回媒体服务器地址(信令服务器和媒体服务器对资源的要求不同,一般来说是两种不同的服务器)
  1. 用户和媒体服务器建连,开始发布、订阅音视频数据

这样一次通话就完成了。大家可以看到,通话过程被分为了两部分,信令交互和媒体交互,因此:RTC 全球化 = 信令全球化 + 媒体全球化。下面我们就分别介绍一下火山引擎 RTC 在媒体全球化上的实践和在信令全球化上的架构演进。

3. 媒体全球化

3.1 机房建设

火山引擎 RTC 在全球建设了大量边缘机房,覆盖 200+ 国家和地区。除自建机房外,全球还有 20+ 边缘云供应商。

图片

3.2 就近接入

建设如此多的边缘机房,目的就是为了让用户能接入到离自己最近、最优质的边缘节点。

这里介绍一下就近接入的逻辑:

图片

  1. 用户请求分配边缘节点。
  1. 中心机房的调度服务从请求中获取用户的真实 IP(脱敏)。
  1. 带着 IP,通过查询 IPIP 数据库,能够拿到用户的地理位置、ISP 等信息。
  1. 在存储边缘节点的数据库中,通过计算距离、是否同运营商等策略,选择最优的一系列节点返回给用户。
  1. 用户接入边缘节点。

就近接入的流程大致如上,得益于边缘节点的广泛覆盖,全球用户到火山引擎 RTC 边缘节点的单向平均延时仅为 70ms。

这里需要注意的是中心将会返回一系列节点,原因会在稳定性部分解释。就近接入优化了用户第一公里的质量,但在实际场景中,仅有就近接入一个策略是不够的。这里要先介绍一个概念:级联,假设两个用户通话,用户分别连接到 A、B 两台媒体服务器上,那么 A、B 服务器之间要互相拉流,服务器间拉流的操作就被称作级联。

假设这样一个场景,一个 4 人会议,User1 和 User2 发言,User3 和 User4 是听众,所有人都连接在不同的媒体服务器上,我们可以数一下,一共将产生 6 路级联流(假设每人只发 1 路音频,不开摄像头)。

图片

级联流过多,会造成大量的带宽浪费,降低系统容量。为了解决这个问题,我们实现了一套边缘聚合的策略。

3.3 边缘聚合

顾名思义,关键在聚合。一个房间内的用户,在质量允许的范围内,会尽量分配同一个边缘节点,还是上面的场景,假设 User1 和 User2 在一个工区,User3 和 User4 在一个工区,那么最终会形成这样的推拉流结构:

图片

级联流的数量从 6 个下降到了 2 个,极大降低了系统的压力。

就近接入与边缘聚合是两个互斥的策略,想要用好他们,关键就在于对不同场景下质量的把控,在两个策略当中找到平衡点,这是一个非常值得深入研究的话题,但限于篇幅,这里不再赘述。

3.4 实时传输网

如上面所举的例子,即使有边缘聚合,级联流量也是不可能完全从系统中消失的。两个人如果距离很远,在保证用户的接入网络质量的前提下,他们之间的通话必然会产生级联。

我们的边缘节点都是运行在公共互联网之上,而公共互联网是一个尽力而为( Best Effort)  的网络,特别是在长距离、跨国传输的场景下,质量是无法得到保证的。该如何保证级联的网络质量呢?

我们在尽力而为的公共互联网上,通过广泛覆盖的边缘节点进行转发,构建了一张 Overlay 网络,提供有 QoS 保障的全球网络传输服务。

图片

实时传输网的主要责任有两个:

  1. 为两点之间提供最优质(延时、丢包,稳定性)的链路进行传输
  1. 在链路故障时,自动切换到可用链路

图片

公网

图片

实时传输网

上面是公网和实时传输网同一时刻的质量对比,可以很明显地看出,实时传输网的传输质量远高于公网(图片右上角有颜色的说明,绿色最优,黑色最差)。

3.5 稳定性

前面讲到,我们的边缘节点数量很多,覆盖很广,这也意味着,节点出问题的情况会很多,稳定性是我们不得不面对的一个问题。这里简单介绍我们的一些稳定性策略:

图片

  1. 同一地区引入多家供应商,减少单点。
  1. 之前提到过,我们会给用户同时下发多个边缘节点地址,边缘可以择优连接,一旦通话过程中节点故障,可以快速切换。
  1. 针对故障节点的负反馈机制,如上图,当 SDK 发现 110.119.120.1 连不通,会进行负反馈上报,中心服务会将此节点拉黑。

4. 信令全球化

信令的全球化设计经过了 5 个版本的迭代,有的设计活到了现在,有的设计被更好的设计覆盖,接下来将介绍整个演进过程。

图片

在信令全球化的设计中,要始终注意两点:

  1. 房间中的用户能互相感知(必须满足),一旦无法感知,相当于房间脑裂,无法感知对方,是失败的设计。
  1. 信令能被尽快处理(极致追求)。

4.1 V1.0: 单中心

图片

初代版本我们在全球只有一个中心机房,所有的信令都通过动态加速回源到这个中心。国内的用户质量还能接受,海外的质量就比较糟糕,连不通也是常有的事情。单中心还有一个更大的问题,如果这个中心机房有故障,RTC 将无法容灾。

这一阶段信令的平均延时是:

  1. 国内 250ms。
  1. 海外 1s+。

因为体验和稳定性的原因,我们在这一阶段只停留了很短的时间,就马上进行了多中心的建设。

4.2 V2.0: 多中心+中心化房间

为了解决 V1.0 版本的容灾问题,我们在全球建设了多个中心机房,并在机房内进行了单元化部署。

图片

多个机房意味着同一房间内的用户可以连到不同的机房,信令全球化开头提到了两个注意点,其中之一是要保证房间内的用户能够互相感知,这一版本我们采用了中心化房间的设计。

中心化房间就是:一个房间(包括房间、用户、流等)的信息只属于一个中心机房,为此我们引入了一个新的服务:region,由它来负责对房间属于哪个中心机房进行决策,region 要保证全球数据的一致性,否则房间会脑裂。

简单描述下中心化房间的信令处理流程:

图片

  1. 用户将信令投递给离他最近的中心机房。
  1. 机房内的 Gateway 从信令消息中提取 AppID,RoomID 等信息,并请求 region 服务。
  1. region 进行房间归属决策(保证一致性),返回给 Gateway。
  1. Gateway 将信令投递给对应机房的信令服务,如果房间不属于本机房,就进行跨机房调用。

注意,只有第一条消息需要请求 region,后面的信令消息根据缓存的房间归属进行投递即可。此方案使就近处理信令成为了可能,比如一个业务用户主要在东南亚,用户连接在新加坡机房,那么我们可以在 region 中针对此业务进行一些配置,让房间决策尽量落在新加坡机房,用户的信令就能直接在本机房处理。

但是如果业务的用户全球都有分布,还是会有大量信令需要进行跨机房投递,延迟依然会比较高,本阶段的平均延时大概为:

  1. 房间属于本机房:300~500ms。
  1. 房间属于其他机房:400ms+(如果跨机房网络抖动,会很差)。

中心化房间还有一些其他问题:

  1. region 需要保证全球一致性,对服务的要求极高,容易成为稳定性和性能的瓶颈。
  1. 因为信令只属于一个机房,当这个机房故障,用户切换到其他机房时,会发现并没有相关的数据,用户需要重新进房、发布、订阅,这对于用户体验也是不够友好的。

4.3 V3.0: 分布式房间

分布式房间的架构可以很好地解决中心化房间的问题。分布式房间一直是我们规划中的架构,但是因为复杂度的问题,并没有立刻投入开发。它的改造要比中心化房间复杂很多,因此先实现了的中心化房间来提升体验。现在,是时候去攀登这座山峰了。

分布式房间是指:一个房间内的信息在多个中心机房内都有完整的数据。因此,我们需要对数据进行全球化同步,此外,中心化房间中的 Gateway 和 region 服务都不再需要了。

图片

整个改造对于业务逻辑调整很大,延时上也取得了很大的进步,这一阶段的信令平均延时在 300ms 左右。受限于篇幅,这里只介绍分布式房间架构中两个比较关键的技术实现。

4.3.1 同步模式

在进行数据同步时会发现,我们的数据量太大了,同步占用的带宽、CPU、存储占用都很多。但是对于一些接入方来说,这些数据同步并没有意义,不会被使用到。

为此我们设计了两种同步模式:全同步与半同步

image.png

解释一下半同步中碰撞的含义,如果同一房间的用户在不同机房加入房间,在互相同步房间数据时会发现,另一个机房已经有了这个房间,这就叫做碰撞。房间数据相较于用户、流等数据量级要小很多,给合适的业务开启半同步,极大减轻了同步的压力。

图片

同步模式自适应切换

全同步与半同步在 RTC 中会自适应切换,我们的离线分析工具会定时分析各个业务方房间的碰撞情况,如果一段时间内碰撞的次数较多,就切到全同步,提升用户体验;如果碰撞次数减少到一定阈值,就降级到半同步。

4.3.2 可靠传输通道

为了保证跨机房同步质量,我们在这几个中心机房之间架设了专线,但是从实际使用看,专线的稳定性并不高,海缆、陆缆都有经常性的故障,并且这种故障修复非常困难,导致故障时间很长。

我们为此开发了一个可靠传输通道,它有如下特点:

  1. 增加公网 relay 节点,摆脱对专线的强依赖。
  1. 多径传输,单条线路故障对传输无影响。
  1. 消息保序(RTC 对信令消息的顺序有极高要求)。

图片

平均一年我们会遇到几十次专线故障,火山引擎 RTC 基本每次都能平安度过。

4.4 V4.0: 统一接入

在V1.0 版本的架构设计中,当时我们使用了动态加速这样一个服务,动态加速的原理是用户的请求直接发送到动态加速供应商的边缘节点,这些边缘节点进行内部转发,最后回源到业务的服务器。既然 RTC 有了覆盖这么广泛的边缘节点,为什么不能用来加速信令消息呢?

当然可以!我们目前已经有了一个和边缘节点通信的媒体通道,让信令通道和媒体通道统一即可,也就是我们现在说的统一接入,信令消息经由边缘节点转发给中心机房,平均延迟优化到了 230ms(优化了 70ms 左右)。

图片

4.5 V5.0: 边缘下沉

“百尺竿头,更进一步”

我们模仿了一套动态加速的实现,效果还不错。但是让我们静下心来想一想,以前“我”是使用动态加速的服务,现在“我”就是动态加速本身呀,这些边缘都是我自己的边缘,只用来做转发是不是太浪费了,为什么不能直接处理信令呢?如果能实现,那么大量请求将在边缘直接返回,延迟将大大优化。

图片

逻辑下沉

这一阶段我们将进房、发布、订阅等逻辑下沉到了到了边缘,加强了 RTC 的边缘计算能力,信令的平均延迟优化到了 100ms。

需要注意的是,虽然逻辑下沉到了边缘,这并不代表中心机房就没有用了,在处理完请求后,会将消息异步发送给中心机房,中心机房具有全局视野,会对信令进行进一步的处理,比如通知房间内的其他用户等。

4.6 稳定性

最后还是讲一下稳定性的话题,上面提到,我们现在在全球是多中心机房的架构,中心机房虽然比边缘机房稳定一些,但也会有故障的困扰,中心机房的故障分为两类:接入故障和服务故障。

4.6.1 接入故障

接入故障分为两类:

  • 接入网络故障,为了提升体验,一般中心机房都是多线 BGP 接入,单线故障时有发生。

图片

  • 解决思路也是多径传输,摆脱对单条线路的依赖。
  • 接入网关故障,比如 nginx 故障,此时所有路径都会失败。

图片

  • 上面提过我们的多机房建设,边缘机房会对多机房进行实时探测,故障时自动切换。

4.6.2 服务故障

服务故障时更常见的一种故障,比如代码的 bug 被触发,强依赖的下游故障,依赖的基础设施(mysql/redis/mq/...)故障等。

图片

我们开发了一套自动运维系统:AIOps,它会实时监控机房的健康度,在健康度下降时自动切换到灾备机房。

4.7 回顾

图片

信令的全球化从初版的“单中心”演进到了当前的“多中心+分布式房间+统一接入+边缘下沉”的架构,在信令的处理延时上取得了显著的进步。

5. 总结

图片

火山引擎 RTC 的全球化架构极大降低了信令和流媒体延时,端到端延时 200ms、400ms 达标率都达到了 99.0% 以上,可以说,在绝大多数的情况下,火山引擎 RTC 都能提供愉悦的实时音视频体验。

另外,得益于我们在稳定性上的大力投入,即使面对边缘节点故障、机房故障、专线故障等突发事件时,火山引擎 RTC 也能够始终提供高质量、高可用的服务。