本文由vivo互联网技术团队LinDu、Li Guolin分享,有较多修订和改动。

IM即时消息模块是直播系统的重要组成部分,一个稳定、有容错、灵活的、支持高并发的消息模块是影响直播系统用户体验的重要因素。

本文针对秀场直播,结合我们一年以来通过处理不同的业务线上问题,进行了技术演进式的IM消息模块架构的升级与调整,并据此进行了技术总结、整理成文,希望借此机会分享给大家。

在目前大部分主流的直播系统中,推拉流是实现直播视频业务最基本的技术点,IM实时消息技术则是实现观看直播的所有用户和主播实现互动的关键技术点。

通过直播系统中的IM消息模块,我们可以完成公屏互动、彩色弹幕、全网送礼广播、私信、PK等核心秀场直播的功能开发。“IM消息”作为用户和用户、用户和主播之间“沟通”的信息桥梁,如何保证“信息桥梁”的在高并发场景下保持稳定可靠,是直播系统演进过程中一个重要的线、系列文章

直播系统聊天技术(一):百万在线的美拍直播弹幕系统的实时推送技术实践之路》

在直播业务中,有几个关于消息模型的核心概念,我们先简单地总结一下,方便大家对直播相关的消息模型有一个整体上的理解。

微信等标准社交IM产品,不管是私聊还是群聊,每个人发送消息的优先级基本上是一样的,不存在谁的消息优先级高,谁的消息优先级低,都需要将消息准确实时地分发到各个业务终端.但是直播因为业务场景的不同,消息分发的优先级也是不一样的。

礼物消息大于公屏消息,同等业务类型的消息,大额礼物的消息优先级又大于小额礼物的消息,高等级用户的公屏消息优先级高于低等级用户或者匿名用户的公屏消息,在做业务消息分发的时候,需要根据实际的消息优先级,选择性地进行消息准确地分发。

如上图所示,我们消息模块中消息的交互方式就是推拉结合。下面将分别详细展开介绍用于“拉”的短轮询和用于“推”的长连接技术。

正如上节中架构图所示,我们的架构中使用上短轮询技术。本节将详细介绍之。(关于短轮询技术的原理,可以看看这篇《

5.1 短轮询的业务模型首先,我们先简单描述一下短轮询的时序逻辑和设计思想:

2)服务器根据roomId和timestamp查询该房间在timestamp时间戳后产生的消息事件,返回限定条数的消息例如(例如返回10~15条,当然在这个timestamp之后产生的消息数远远大于15条,不过因为客户端渲染能力有限和过多的消息展示,会影响用户体验,所以限制返回的条数),并且同时返回这些消息中最后一条消息产生的时间戳timestamp,作为客户端下次请求服务器的基准请求时间戳;

3)以此反复,这样就可以每隔2s按照各个终端要求,更新每个直播间的最新消息了。

整体的技术逻辑如上图所示,不过具体的时序可以再做精细化处理,后续再做具体的说明和细节说明。

短轮询的消息存储与正常的长连接的消息存储有一定的区别,因为它不存在消息扩散的问题。

结合上述4点的技术要求,经过小组成员的讨论,我们决定使用Redis的SortedSet数据结构进行存储。

一个直播间的消息使用四个Redis的SortedSet数据结构进行存储。

score分别是消息真实产生的时间戳,value就是序列化好的json字符串。

很多同学会疑问,为什么不适用Redis的list的数据结构呢?如下图会进行详细的说明:

短轮询的时间控制及其重要,我们需要在直播观众观看体验QoE和服务器压力之间找到一个很好的平衡点。

会导致服务器的压力过大,也会出现很多次空轮询,所谓的空轮询就是无效轮询,也就是在上一秒有效轮询返回有效消息之后,间隔期直播间没有产生新的消息,就会出现无效的轮询。

vivo直播目前每日的轮询次数是10+亿次,晚观看直播高峰期的时候,服务器和Redis的CPU负载都会上升,dubbo的服务提供方的线程池一直处于高水位线上。这块需要根据机器的和Redis的实时负载的压力,做服务器的水平扩容和Redis Cluster的节点扩容,甚至让一些超高热度值的直播间负载到指定的Redis Cluster集群上,做到物理隔离,享受到“VIP”服务,确保各个直播间的消息相互不影响。

例如人数比较少的直播,百人以下的直播间,可以设置比较高频的轮询频率(比如1.5s左右);

这些配置应该都可以通过配置中心实时下发,客户端能够实时更新轮询的时间,调整的频率可以根据实际直播间用户体验的效果,并且结合服务器的负载,找到一个轮询间隔的相对最佳值。

这一点非常重要,试想一下,如果观众在观看直播的时候,将直播退出后台,客户端轮询进程暂停,当用户恢复直播观看画面进程的时候,客户端传递过来的时间就会是非常老旧甚至过期的时间,这个时间会导致服务器查询Redis时出现慢查。如果出现大量的服务器慢查的话,会导致服务器连接Redis的连接无法快速释放,也会拖慢整个服务器的性能,会出现一瞬间大量的轮询接口超时,服务质量和QoE会下降很多。

在极端情况下,客户端有可能收到重复的消息,产生的原因可能如下,在某一个时刻客户端发出roomId=888888×tamp=t1的请求,因为网络不稳定或者服务器GC的原因,导致该请求处理比较慢,耗时超过2s,但是因为轮询时间到了,客户端又发出了roomId=888888×tamp=t1的请求,服务器返回相同的数据,就会出现客户端重复渲染相同的消息进行展示。

设想一下,如果一个热度极大的直播间,每秒钟产生的消息量是数千或者上万的时候,按照上面的存储和查询思路是有漏洞的。因为我们每次因为各个因素的限制,每次只返回10~20条消息,那么我们需要很长的时间才能把这热度很多的一秒钟的数据全部返回,这样就会造成最新的消息无法快速优先返回。

客户端轮询服务服务器查询直播间的消息的好处是显而易见的,消息的分发是非常实时和准确的,很难出现因为网络颤抖导致消息无法到达的场景。

不过坏处也是非常明显的,服务器在业务高峰期的负载压力很大,如果直播间的所有消息都是通过轮询分发,长期以往,服务器是很难通过水平扩容的方式来达到线性增长的。

手机客户端首先通过http请求长连接服务器,获取TCP长连接的IP地址,长连接服务器根据路由和负载策略,返回最优的可连接的IP列表;

2)手机客户端根据长连接服务器返回的IP列表,进行长连接的客户端的连接请求接入,长连接服务器收到连接请求,进而建立连接;

为了使消息即时、高效、安全地触达用户,直播客户端和IM系统建立了一条加密的全双工数据通路,收发消息均使用该通道,当大量用户在线的时候,维护这些连接、保持会话,需要用到大量内存和CPU资源。

连接维护、账号管理等不经常改动的基础逻辑放入主程序中,业务逻辑采用so插件的方式嵌入到程序的,修改业务逻辑时只需要重新加载一次插件即可,可以保证与设备的长连接不受影响。

长连接建立后,如果中间网络断开,服务端和客户端都无法感知,造成假在线的情况。

因此维护好这个“长连接”的一个关键的问题在于能够让这个“长连接”能够在中间链路出现问题时,让连接的两端能够快速得到通知,然后通过重连来建立新的可用连接,从而让我们这个长连接一直保持高可用状态。

利用TCP的keeplive保活探测功能,可以探知客户端崩溃、中间网络端开和中间设备因超时删除连接相关的连接表等意外情况,从而保证在意外发生时,服务端可以释放半打开的TCP连接。

Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?》

在整合客户端、IM长连接服务器模块和直播业务服务器模块这三个模块的时候,整体消息的分发逻辑遵循几个基本原则。

单聊、群聊、广播消息所有的消息都是由直播业务服务器调用IM长连接服务器的接口,将需要分发的消息分发到各个业务直播间;

2)业务服务器对直播间产生的事件进行对应的业务类型做响应的处理,例如送礼扣减虚拟货币,发送公屏进行文本健康校验等;

采用固定分片的方式算法实现简单,但是对于用户少的直播间有可能分片承载的用户数量少,对于用户大的直播间有可能分片承载用户量又比较大,固定分片存在天然伸缩性差的特点。

规定分片用户数,当用户数超过阈值时,增加一个新的分片,分片数量可以随着用户数增加而变化。

动态分片可以根据直播间人数自动生成分片,满了就开辟新片,尽量使每个分片的用户数达到阈值,但已有分片的用户数量随着用户进出直播间变化,维护复杂度比较高。

直播间中有进出场消息、文本消息、礼物消息和公屏消息等多种多样消息。消息的重要程度不一样,可为每个消息设定相应的优先级。

不同优先级的消息放在不同的消息队列中,高优先级的消息优先发送给客户端,消息堆积超过限制时,丢弃最早、低优先级的消息。

根据直播间成员分片通知对应的消息发送服务,再把消息分别下发给分片中对应的每一个用户。为了实时、高效地把直播间消息下发给用户,当用户有多条未接收消息时,下发服务采用批量下发的方式将多条消息发送给用户。

如果某一个时刻,分发消息的数量比较大,或者同一个消息在做群播场景的时候,群播的用户比较多,IM连接层的机房的出口带宽就会成为消息分发的瓶颈。

所以如何有效的控制每一个消息的大小、压缩每一个消息的大小,是我们也需要思考的问题。

经过我们线上测试,使用protobuf数据交换格式,平均每一个消息节省43%的字节大小,可以大大帮助我们节省机房出口带宽。(关于protubuf的更多资料,请阅读《

)7.5 块消息所谓块消息,也是我们借鉴其他直播平台的技术方案,也就是多个消息进行合并发送。

直播业务服务器不是产生一个消息就立马调用IM长连接服务器集群直接进行消息的分发。

减少传输消息头:合并消息,可以减少传输多余的消息头,多个消息一起发送,在自定义的TCP传输协议中,可以共用消息头,进一步减少消息字节数大小;

2)防止消息风暴:直播业务服务器可以很方便的控制消息分发的速度,不会无限制的分发消息到直播客户端,客户端无法处理如此多的消息;

客户端通过长连接获取的消息突增,下行带宽压力突增,其他业务可能会受到影响(例如礼物的svga无法及时下载播放);

2)客户端无法快速处理渲染如此多的礼物和公屏消息,CPU压力突增,音视频处理也会受到影响;

礼物的优先级一定是高于公屏消息的,PK进度条的消息一定是高于全网广播类消息的,高价值礼物的消息又高于低价值礼物的消息。

选择性丢弃低优先级消息:结合具体业务特点,给各个业务类型的消息划分出不同等级,在消息分发触发流控的时候,根据消息优先级选择性丢弃低优先级消息;

2)选择性丢弃“老”消息:消息结构体新增创建时间和发送时间两个字段,在实际调用长连接通道的时候,需要判断当前时间与消息的创建时间是够间隔过大,如果过大,则直接丢弃消息;

举例来说,9点10的消息,主播A和主播B的PK值是20比10,那么9点11分分发的PK消息值就是22比10,而不能分发增量消息2:0,希望客户端做PK条的累加(20+2 :10+0)。但是存在消息因为网络颤抖或者前置消息丢弃,导致消息丢弃,所以分发增益消息或者纠正消息会能够帮助业务重新恢复正常。

9、写在最后任何一个直播系统,随着业务的发展和直播间人气不断的增加,消息系统遇到的问题和挑战也会随之而来。不管是长连接的消息风暴,还是海量http短轮询的请求,都会导致服务器压力的剧增,都是我们需要不断解决和优化的。

我们要针对每一个时期的业务特点,做直播消息的持续升级,做可演进的IM消息模块,确保消息分发的能力能够确保业务的持续发展。

vivo直播消息模块也是逐步演进的,演进的动力主要来自于因为业务的发展,随着业务形态的多样化,观看的用户数越来越多,系统的功能也会逐步增多,也会遇到各种性能瓶颈,为了解决实际遇到的性能问题,会逐一进行代码分析,接口性能瓶颈的分析,然后给出对应的解决方案或者解耦方案,消息模块也不例外。

作者 yabo394

发表回复

您的电子邮箱地址不会被公开。