火山引擎RTC助力抖音百万并发“云侃球”

时间:2022-12-09 10:04:35

   1. 背景及技术挑战

  从电视看直播到手机电脑看直播,直播技术的发展让观众可以随时、随地观看自己喜欢的比赛,并且在看比赛时通过发送表情、发文字进行互动。但表情、文字承载的信息量较小、沟通效率低,我们无法像线下一起看比赛那样和好友边看边聊、一起为精彩的比赛呐喊,观赛体验大打折扣。

  为了让观众获得更好的观赛体验,抖音在 2022 世界杯比赛直播中推出了“边看边聊”的玩法:每个观众都可以邀请好友(或分享聊天频道信息邀请)一起观看世界杯比赛;在频道中,好友既可以发送文字、表情聊天,还可以上麦进行语音聊天,一起为精彩进球欢呼,大大提升“异地好友线上观赛”的体验。  

火山引擎RTC助力抖音百万并发“云侃球”

进入“抖音体育”直播间,邀请好友加入个人聊天频道,即可以和好友“边看边聊”

  我们使用 RTC 来实现“边看边聊”的功能——观众可以随时上麦进行语音聊天,同时频道中的普通观众也可以听到麦上用户的精彩评论。但在抖音“边看边聊世界杯”的玩法中,RTC 面临着几个比较大的挑战:

  一是高并发的问题,包括音视频流数高并发和大量进退房请求对系统的冲击。世界杯是四年一度的体育盛会,会有大量用户同时在线看球聊球,这为 RTC 房间带来了持续高并发的音视频推拉流压力,对于系统的性能及稳定性提出了巨大挑战。同时,在比赛开始和比赛结束时,短时间内大量用户进房、退房的请求也会对 RTC 系统形成冲击 。

  二是观赛中的音视频体验问题。 包括外放时,比赛的声音被麦克风采集并发送到远端形成回声的问题;通话人声响度低于直播声音响度导致听不清问题;整体音质优化等难点问题。

   2. 整体方案设计

  抖音“边看边聊”的玩法允许单房间 500 人加入,每个房间允许 9 个用户上麦聊天,另外 491 个未上麦用户只旁听不发言。在整体方案设计的过程中,火山引擎 RTC 考虑了“语音聊天室方案”和“ RTC 互动语聊方案”两种方案,并对两种方案架构进行了分析。

  2.1 语音聊天室方案

  方案选型初期,其中一个候选方案是“在直播间中再嵌入一个语音聊天室”,即,在观看比赛直播的同时,上麦用户加入 RTC 进行语音聊天,其余未上麦观众再多拉一路 CDN 流收听聊天内容, 整体结构如下:  

火山引擎RTC助力抖音百万并发“云侃球”

  该方案的优势是可以快速复用线上业务语音聊天室的主播、观众代码,以及复用当前线上业务中上麦、下麦流程,快速搭建该玩法场景。但这个方案也存在一个问题, 即未上麦的用户(图中好友 C、好友 D)听到的聊天内容会有较大的延时, 麦下用户会在进球后大约 1~3 秒(CDN 转推延时)才能听到麦上用户聊进球相关内容,这会导致比赛进程画面和聊天内容不同步、聊天内容延时大,对于需要“同频共振”的大型赛事观播是无法接受的体验。

  2.2 RTC 互动语聊方案

  为了保证所有用户“边看边聊、精彩共享”的核心体验,“边看边聊”玩法选择了“ RTC 互动语聊”的方案,即所有用户都加入 RTC 房间,使用火山引擎 RTC 为支持超大型视频会议、在线教育大班课场景打造的“千人上麦”和“稳定支持超百万人同时在线”能力,来应对百万人并发量级的世界杯“边看边聊”需求。

  方案整体架构如下:  

火山引擎RTC助力抖音百万并发“云侃球”

  方案核心要点如下:

  (1)观众使用播放器从 CDN 拉取高清比赛直播流,播放器支持用户手动选择清晰度档位、支持 seek 观看高光时刻等操作;

  (2)频道中好友通过 RTC 进行实时音频通话;

  (3)RTC 支持将通话的语音切片后存储,供业务进行审核,保障通话内容安全;同时,平台还可以通过踢人/拉黑等运维 OpenAPI,处理不合规或不符合业务预期的用户,保障平台运营安全。

  在确认整体方案架构后,我们重点对如何应对超高并发、如何提升边看边聊体验进行了深度优化。

   3. 关于超高并发问题的优化和实践

  “超高并发”是本次世界杯“边看边聊”场景的最大挑战,由于比赛活动时间集中,相关流量都会集中出现在 64 场比赛的时间段,特别是在开幕战、明星球队出战、决赛等热门比赛场次,会有超大量观众同时进入直播间;而且,用户在比赛开始前后集中上线、比赛结束后集中离线也会对 RTC 系统稳定运行形成巨大压力。

  3.1 关于流数高并发的优化

  世界杯“边看边聊”场景的特点是流量大,DAU 高(预估峰值将超过百万观众同时“边看边聊”),抽象到 RTC 场景,就是房间数量多,每个房间的用户数也多。因此,我们设计了一套高效率的方案,来兼顾用户实时交互体验以及承载更多量级用户的需求。

  3.1.1 常规方案

  首先是常规方案。在常规方案中,服务器只需要转发流,不需要做过多额外的处理,用户在最后一公里就近接入,服务器之间做级联。在这种架构下,用户的实时交互体验很好,但对于大房间(用户多)来说不够合理,用户订阅压力大,客户端面临一定的拉流压力,服务端也面临性能和容量的双重挑战。

  常规方案架构图如下:  

火山引擎RTC助力抖音百万并发“云侃球”

  以“500 人房间”为例,单房间 500 个用户看球,其中 9 个人开麦聊天,订阅端每个用户需要订阅 9 路流,用户下行拉流过多,对用户的客户端造成一定的性能压力。再说服务端,假设考虑到服务器之间级联,在最差情况下(500 个用户连接到 500 个不同服务器节点,服务器之间都需要级联转发),平均一个用户会给整个系统带来 10+ 路媒体流。再考虑到这个场景的 DAU,这套常规方案对服务端性能、容量都会造成很大的压力。

  3.1.2 公共流扩展方案

  常规方案在房间人数多时会面临诸多性能压力,因此我们又设计了一套公共流扩展方案。在公共流方案中,发布端(上麦用户)仍然使用常规方案的设计,媒体服务器只需要纯转发,不需要做过多额外的处理;订阅端(闭麦用户)则订阅经过 MCU(Multipoint Control Units,多点控制单元)服务器处理的公共流,在这个架构下,用户的订阅流数降低至 1 路。  

火山引擎RTC助力抖音百万并发“云侃球”

  同样以“500 人房间”为例,单房间 500 个用户看球,其中 9 个人开麦聊天,订阅端每个用户只需要订阅 1 路流,释放了客户端使用压力。对于服务端来说,平均一个用户只会给整个系统只会带来 2-3 路媒体流的增加,极大优化了服务端资源消耗。

  这套方案架构可以很好地解决热流分发的压力,相同的服务器资源可以承载更大的容量,单流可支持 10w 量级的并发订阅。客户端也因为订阅变少,性能得到很大的提升。但这套方案架构也会对用户的交互体验产生影响,当用户从“只订阅的角色”切换至“发布 + 订阅”的角色时,用户需要先切换到“常规方案”,即“从公共流”进入到“RTC 房间”,这个时候,用户的流内容会发生切换,用户会有“卡顿一下”的感觉。如果这个用户频繁地切换角色,那就会频繁地感觉到“卡顿”,用户体验反而恶化。

  3.1.3 融合方案

  常规方案带给用户的交互性好,但是增加了大多数订阅端用户的设备端性能压力以及服务端的资源消耗;公共流方案减少了 RTC 系统全链路并发音视频流数,缓解了订阅端用户的性能压力,但是在频繁上下麦时,频繁地“常规方案”和“公共流方案”之间切换会导致户体验受损。基于以上特点,火山引擎 RTC 在抖音“边看边聊”场景中设计了一套“有房间+公共流”的融合方案,来兼顾用户体验和设备端、服务端性能优化。

  融合方案设计流程如下:  

火山引擎RTC助力抖音百万并发“云侃球”

  具体流程为:

  用户进房

  a. 当一个聊天频道的用户小于 M 人时,该房间使用“常规方案”,用户使用“静默用户”的身份进入 RTC 房间并订阅流;

  b. 当一个聊天频道的用户大于等于 M 人时,用户使用“公共流扩展方案”加入。

  首次上麦

  a. 当用户以常规 RTC 方案订阅流时,上麦时用户改变状态,静默用户 -> 非静默用户;

  b. 当用户以公共流方式订阅流时,上麦时用户以非静默用户身份直接进入 RTC 房间。

  二次上麦

  a. 用户改变状态,静默用户 -> 非静默用户。

  用户下麦

  a. 用户改变状态,非静默用户 -> 静默用户。

  将常规方案与公共流方案结合的方案融合了两者的优点:

  (1)用户默认以订阅公共流的方式加入“大房间”中,可以减少 RTC 系统全链路的并发音视频流数,扩大 RTC 系统并发容量;

  (2)能有效减少用户在不拉流时候的设备端性能压力;

  (3)用户在上麦时切换为常规 RTC 方案“有房间”的模式,可以保证用户实时的交互音视频体验。在切换为有房间模式后,后续的上下麦则不会再变更模式,保证了用户的平滑体验。

  3.2 系统容灾保护

  抖音 DAU 很大,参与“边看边聊”玩法的用户并发峰值超过百万量级,并且,世界杯类大型赛事活动有个特征——在比赛开始的时候,用户集中进房;比赛结束时刻,用户又会集中停止使用音视频功能,所以在比赛开始和结束的时候会有大量请求发到 RTC 云端服务器,对云服务造成很大压力,极端情况下甚至会导致服务异常。针对进房和退房的不同特点,火山引擎 RTC 分别采用了“多级限流”的进房保护策略和“延时处理”的退房保护策略。

  3.2.1 进房多级限流保护

  火山引擎 RTC 采用“边缘+中心”结构,用户就近接入边缘节点,数据则存储在中心机房。在做限流保护的时候,我们也采用了相似的策略,即多级保护、分段限流,包括全局分布式 QPS 限流,中心 QPS 限流,中心房间数限流。

  架构如下:  

火山引擎RTC助力抖音百万并发“云侃球”

  全局分布式 QPS 限流

  全局分布式 QPS 限流采用滑动窗口算法实现。中心信令通过存储维护每秒可以消耗的令牌数量,边缘节点定时向中心同步自己的令牌数量,同时中心返回当前时间戳内消耗的总令牌数。进房时刻的尖峰流量对中心节点不友好,全局分布式 QPS 限流可以保证限流平滑,即使部分节点有瞬时尖峰流量,整个系统也不会受到过大冲击。

  中心 QPS 限流

  中心 QPS 限流采用令牌桶算法实现。中心信令以恒定的速率产生令牌,然后把令牌放到令牌桶中,令牌桶有一个容量,当令牌桶满了后,如果再向其中放入令牌,多余令牌就会被丢弃。当中心信令想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么该请求就会被拒绝,客户端会收到服务端返回的错误码提示。

  中心房间数限流

  中心信令会在存储中维护当前系统可以承载的最大房间数量,每当新用户使用火山引擎 RTC 之后,中心信令就会查询存储判断当前的房间数量是否已到达上限,如果超过了,则会拒绝本次用户的请求,客户端会收到服务端返回的错误码提示。

  全局分布式 QPS 限流、中心 QPS 限流,中心房间数限流“三管齐下”的进房多级限流保护措施解决了“边看边聊”场景大流量对整个系统的威胁。云服务系统在处理高并发请求时,先进行全局分布式 QPS 限流,然后再进行中心 QPS 限流,当整体系统处于高水位时,又会采取全局分布式房间数限流。

  3.2.2 退房/断连延时处理保护

  进房操作对实时性要求很高,如果进房慢,用户的体验也会严重受损。不同于进房操作,用户可以在一定程度上忍受“退房慢”,因此服务端的保护策略上也和进房略有不同。退房/断连保护的策略核心是“延时处理”,在边缘节点设置一个定长的 FIFO 队列,每个边缘节点的请求先进入 FIFO 队列中,后续按照一定的速率重新发送到中心信令。经过这样的保护,服务可以处理超百万 QPS 的退房操作。  

火山引擎RTC助力抖音百万并发“云侃球”

  退房/断连保护的基本执行步骤:

  检测用户退房事件(包括用户正常离开房间与断网离房)的 QPS,如果 QPS 超过可以立即处理的阈值,将事件触发的上下文保存到队列中,且记录当前时间戳到事件的上下文中;

  在队列中启动一个 Loop,尝试获取队列中请求任务去执行,每次在执行前,还会检查退房事件触发事件的时间戳与当前时间差是否小于某个定义的阈值,小于该阈值的请求会被执行发送到中心信令;大于等于该阈值的请求则会被丢弃;

  在发送成功后,从队列中删除该事件的上下文信息。

  异常场景考虑

  用户发送退房请求,且被拦截保存到缓存队列中,之后很短时间内用户又重新进房,那么会出现用户退房事件与用户下次进房存在时序问题,我们用引入“退房时间戳”来解决这个问题。中心信令在接收到用户退房请求时,会比较当前用户进房时间戳与退房时间戳,若进房时间晚于退房时间,说明是用户退房后再进房,系统可以直接忽略该用户的离房请求。

  退房请求放入队列超过一定时长后可能会触发断连请求。这里的处理方式就是依次处理退房请求和断连请求,如果用户已经退房,则忽略断连请求。

   4. 优秀的用户体验

  优秀的音视频体验是业务玩法获得用户认可的必要条件。在边看边聊场景中,我们面对着本地直播音频被麦克风采集形成回声、使用通话模式导致直播音质变差、比赛声音比聊天声音大导致人声听不清楚等问题,这些问题都严重影响了用户的边看边聊体验;为了解决相关问题,我们使用了直播音频托管 RTC 播放、全链路音频媒体通道模式、智能音频闪避等方案,为边看边聊提供了良好的音视频体验。

  4.1 音频托管

  回声消除问题是 RTC 的重点和难点问题;在边看边聊场景中,部分用户会使用外放音频方式来观看比赛, 在这种情况下,RTC 播放的远端人声和直播播放器播放的比赛声音会被麦克风采集后发送到远端形成回声。

  为了解决边看边聊场景的回声问题,RTC 和播放器提供了播放器音频托管由 RTC 来播放的解决方案,由播放器将解码后的直播音频数据调用 RTC 音频托管接口来播放;在 RTC 内部,直播音频会和远端用户音频进行混合,再调用系统音频播放接口进行播放,同时将混合信号送到 RTC 回声消除模块,回声消除模块会将麦克风采集到的声音中的远端音频和直播比赛声音消除,这样就得到了没有回声的本地人声数据,这些数据会在编码后送到远端,避免了回声问题的出现。  

火山引擎RTC助力抖音百万并发“云侃球”

  4.2 外放媒体模式

  在移动端设备上,音频播放区分通话模式(通话模式音频通道)和媒体模式(媒体模式音频通道),二者在音质表现、音量控制上略有不同,从而适用于不同的业务场景, 具体表现如下:  

火山引擎RTC助力抖音百万并发“云侃球”

  由于边看边聊场景是在直播间观看比赛过程中开启音频通话,我们既要保障通话没有回声,也要保障直播音频音质;我们分别对边看边聊场景 RTC 使用媒体通道和通话通道进行了对比测试结果如下:  

火山引擎RTC助力抖音百万并发“云侃球”

  为了给用户提供更好的音质体验,本次边看边聊场景配置了外放媒体模式;为了解决外放媒体模式时系统回声消除效果差的问题,火山引擎 RTC 引入了基于深度学习的回音消除算法,对传统算法难以覆盖的场景进行音质提升,比如信号回声比较大的情况,非线性失真加大的情况,以及音乐场景等,在保障聊天回声消除条件下,达到了高音质体验。  

火山引擎RTC助力抖音百万并发“云侃球”

  4.3 智能音频闪避

  边看边聊场景的另外一个特点是直播流中现场声音、解说声音的音量通常会比好友之间聊天声音音量大,这就造成了聊天的声音偏小或者几乎听不到远端好友声音的问题;为了解决直播声音大而聊天声音比较小的问题,我们在边看边聊场景中调整了远端人声和直播声音的音量配比,保障远端聊天人声和直播声音响度基本持平。

  为了更进一步避免比赛声音和远端人声冲突导致无法听清聊天内容的问题, 我们引入了智能音频闪避算法。音频闪避(Audio Ducking)的功能是当检测到 A 信号出现时,将 B 信号的电平降低,仿佛 B 信号「躲避」了 A 信号,因此得名「闪避」。闪避算法非常适合在「边看边聊」和「游戏直播」场景中开启,在边看边聊场景中,A 信号是远端用户人声,B 信号是播放器播放的比赛声音。开启闪避功能后,RTC 在收到远端语音时,将播放的直播声音进行闪避,能让用户更清晰地听到远端好友的语音, 经过验证,达到了非常好的音频体验。

  边看边聊场景添加智能音频闪避后的音频处理流程如图:  

火山引擎RTC助力抖音百万并发“云侃球”

  关于智能音频闪避功能中的音频增益控制,还有一些经验性原则需注意:

  增益下降应足够快,否则语音的开始片段仍会被音乐掩蔽;但又不能过快,导致出现音质问题;

  增益下降后,应该保持足够的时间,等待人声消失一段时间后再恢复,否则,正常讲话的停顿会频繁触发闪避效果,体验很差;

  增益的恢复可以稍慢,不要给人很突然的感觉;

  要对远端人声进行智能识别检测,避免远端噪声引起过度闪避。

   5. 总结与展望

  火山引擎 RTC 边看边聊场景解决方案, 通过 RTC 公共流 + RTC 有房间无缝切换的方案,在兼顾实时音视频体验基础上,支持了单流超大规模的并发,降低了用户拉流数量,不仅提高了观赛机型渗透率也提高了 RTC 系统容量;针对世界杯观赛用户集中进房 ,集中退房的特点,RTC 服务端制定了 “边缘限流”,“中心限流” ,“信令平滑发送” 等重保策略,提高了 RTC 服务在高 QPS 场景下的稳定性;使用直播流音频托管给 RTC 播放的方案,解决了双端同时播放直播流音频引入的回声问题;使用外放媒体模式 + 软件 3A 方案,在兼顾回声消除基础上保障了高音质边看边聊体验;通过调整音量配比和智能音频闪避功能,解决了直播流声音大, 聊天声音小的问题。经线上打磨验证,方案设计合理有效,为世界杯边看边聊观赛体验提供了有力的保障。

  更进一步,在一起看短视频、一起看电影等场景,业务还可以通过实时信令(RTS)来对房间中各个用户观看进度进行集中控制,保障房间中用户观看相同内容;业务还可以选择打开视频, 更进一步增加好友之间的观影、观赛体验;在UGC 大咖解说场景,我们还可以支持用户上麦与主播进行互动聊天,更进一步拉近主播和观众的距离,实现更好的互动效果。