直播系统聊天技术(七):直播间海量聊天消息的架构设计难点实践

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 本文将主要从高可用、弹性扩缩容、用户管理、消息分发、客户端优化等角度,分享直播间海量聊天消息的架构设计技术难点的实践经验。

本文由融云技术团队原创分享,有修订和改动。

1、引言

在视频直播场景中,弹幕交互、与主播的聊天、各种业务指令等等,组成了普通用户与主播之间的互动方式。

从技术的角度来看,这些实时互动手段,底层逻辑都是实时聊天消息或指令的分发,技术架构类比于IM应用的话,那就相当于IM聊天室功能。

本系列文章的上篇《百万人在线的直播间实时聊天消息分发技术实践》主要分享的是消息分发和丢弃策略。本文将主要从高可用、弹性扩缩容、用户管理、消息分发、客户端优化等角度,分享直播间海量聊天消息的架构设计技术难点的实践经验。

cover-opti.png

2、系列文章

本文是系列文章中的第7篇:

3、直播间的主要功能和技术特征

如今的视频直播间早已不单纯是视频流媒体技术问题,它还包含了用户可感知的多类型消息发送和管理、用户管理等任务。在万物皆可直播的当下,超大型直播场景屡见不鲜,甚至出现了人数无上限的场景,面对如此海量实时消息和指令的并发挑战,带来的技术难度已非常规手段所能解决。

我们先来归纳一下如今的典型视频直播间,相较于传统直播间所包含的主要功能特征、技术特征等。

丰富的消息类型和进阶功能:

  • 1)可发送文字、语音、图片等传统聊天功能;
  • 2)可实现点赞、礼物等非传统聊天功能的消息类型;
  • 3)可管理内容安全,包括敏感词设置,聊天内容反垃圾处理等。

聊天管理功能:

  • 1)用户管理:包括创建、加入、销毁、禁言、查询、封禁(踢人)等;
  • 2)用户白名单:白名单用户处于被保护状态不会被自动踢出,且发送消息优先级别最高;
  • 3)消息管理:包括消息优先级、消息分发控制等;
  • 4)实时统计及消息路由等能力。

人数上限和行为特征:

  • 1)人数没有上限:一些大型直播场景,如春晚、国庆大阅兵等,直播间累计观看动辄上千万人次,同时观看人数也可达数百万;
  • 2)用户进退行为:用户进出直播间非常频繁,高热度直播间的人员进出秒并发可能上万,这对服务支撑用户上下线以及用户管理的能力提出了非常大的挑战。

海量消息并发:

  • 1)消息并发量大:直播聊天室人数没有明显上限,带来了海量并发消息的问题(一个百万人数的聊天室,消息的上行已是巨量,消息分发量更是几何级上升);
  • 2)消息实时性高:如果服务器只做消息的消峰处理,峰值消息的堆积会造成整体消息延时增大。

针对上述第 2) 点,延时的累积效应会导致消息与直播视频流在时间线上产生偏差,进而影响用户观看直播时互动的实时性。所以,服务器的海量消息快速分发能力十分重要。

4、直播间聊天室的架构设计

高可用系统需要支持服务故障自动转移、服务精准熔断降级、服务治理、服务限流、服务可回滚、服务自动扩容 / 缩容等能力。

以服务高可用为目标的直播间聊天室系统架构如下:

1.png

如上图所示,系统架构主要分三层:

  • 1)连接层:主要管理服务跟客户端的长链接;
  • 2)存储层:当前使用的是 Redis,作为二级缓存,主要存储聊天室的信息(比如人员列表、黑白名单、封禁列表等,服务更新或重启时,可以从 Redis 中加载出聊天室的备份信息);
  • 3)业务层:这是整个聊天室的核心,为了实现跨机房容灾,将服务部署在多个可用区,并根据能力和职责,将其分为聊天室服务和消息服务。

聊天室服务和消息服务的具体职责:

  • 1)聊天室服务:主要负责处理管理类请求,比如聊天室人员的进出、封禁 / 禁言、上行消息处理审核等;
  • 2)消息服务:主要缓存本节点需要处理的用户信息以及消息队列信息,并负责聊天室消息的分发。

在海量用户高并发场景下,消息分发能力将决定着系统的性能。以一个百万级用户量的直播间聊天室为例,一条上行消息对应的是百万倍的分发。这种情况下,海量消息的分发,依靠单台服务器是无法实现的。

我们的优化思路是:将一个聊天室的人员分拆到不同的消息服务上,在聊天室服务收到消息后向消息服务扩散,再由消息服务分发给用户。

以百万在线的直播间聊天室为例:假设聊天室消息服务共 200 台,那平均每台消息服务管理 5000 人左右,每台消息服务在分发消息时只需要给落在本台服务器上的用户分发即可。

服务落点的选择逻辑:

  • 1)在聊天室服务中:聊天室的上行信令是依据聊天室 ID 使用一致性哈希算法来选择节点的;
  • 2)在消息服务中:依据用户 ID 使用一致性哈希算法来决定用户具体落在哪个消息服务。

一致性哈希选择的落点相对固定,可以将聊天室的行为汇聚到一个节点上,极大提升服务的缓存命中率。

聊天室人员进出、黑 / 白名单设置以及消息发送时的判断等处理直接访问内存即可,无须每次都访问第三方缓存,从而提高了聊天室的响应速度和分发速度。

最后:Zookeeper 在架构中主要用来做服务发现,各服务实例均注册到 Zookeeper。

5、直播间聊天室的扩缩容能力

5.1 概述

随着直播这种形式被越来越多人接受,直播间聊天室面对人数激增致使服务器压力逐步增大的情况越来越多。所以,在服务压力逐步增大 / 减少的过程中能否进行平滑的扩 / 缩容非常重要。

在服务的自动扩缩容方面,业内提供的方案大体一致:即通过压力测试了解单台服务器的瓶颈点 通过对业务数据的监控来判断是否需要进行扩缩 → 触发设定的条件后报警并自动进行扩缩容。

鉴于直播间聊天室的强业务性,具体执行中应该保证在扩缩容中整体聊天室业务不受影响。

5.2 聊天室服务扩缩容

聊天室服务在进行扩缩容时,我们通过 Redis 来加载成员列表、封禁 / 黑白名单等信息。

需要注意的是:在聊天室进行自动销毁时,需先判断当前聊天室是否应该是本节点的。如果不是,跳过销毁逻辑,避免 Redis 中的数据因为销毁逻辑而丢失。

聊天室服务扩缩容方案细节如下图所示:

2.png

5.3 消息服务扩缩容

消息服务在进行扩缩容时,大部分成员需要按照一致性哈希的原则路由到新的消息服务节点上。这个过程会打破当前的人员平衡,并做一次整体的人员转移。

1)在扩容时:我们根据聊天室的活跃程度逐步转移人员。

2)在有消息时:[消息服务会遍历缓存在本节点上的所有用户进行消息的通知拉取,在此过程中判断此用户是否属于这台节点(如果不是,将此用户同步加入到属于他的节点)。

3)在拉消息时:用户在拉取消息时,如果本机缓存列表中没有该用户,消息服务会向聊天室服务发送请求确认此用户是否在聊天室中(如果在则同步加入到消息服务,不在则直接丢掉)。

4)在缩容时:消息服务会从公共 Redis 获得全部成员,并根据落点计算将本节点用户筛选出来并放入用户管理列表中。

6、海量用户的上下线和管理

聊天室服务:管理了所有人员的进出,人员的列表变动也会异步存入 Redis 中。

消息服务:则维护属于自己的聊天室人员,用户在主动加入和退出房间时,需要根据一致性哈希算出落点后同步给对应的消息服务。

聊天室获得消息后:聊天室服务广播给所有聊天室消息服务,由消息服务进行消息的通知拉取。消息服务会检测用户的消息拉取情况,在聊天室活跃的情况下,30s 内人员没有进行拉取或者累计 30 条消息没有拉取,消息服务会判断当前用户已经离线,然后踢出此人,并且同步给聊天室服务对此成员做下线处理。

7、海量聊天消息的分发策略

直播间聊天室服务的消息分发及拉取方案如下图:

3.png

7.1 消息通知的拉取

在上图中:用户 A 在聊天室中发送一条消息,首先由聊天室服务处理,聊天室服务将消息同步到各消息服务节点,消息服务向本节点缓存的所有成员下发通知拉取(图中服务器向用户 B 和用户 Z 下发了通知)。

在消息分发过程中,server 做了通知合并。

通知拉取的详细流程为:

  • 1)客户端成功加入聊天,将所有成员加入到待通知队列中(如已存在则更新通知消息时间);
  • 2)下发线程,轮训获取待通知队列;
  • 3)向队列中用户下发通知拉取。

通过这个流程可保障下发线程一轮只会向同一用户发送一个通知拉取(即多个消息会合并为一个通知拉取),有效提升了服务端性能且降低了客户端与服务端的网络消耗。

7.2 消息的拉取

用户的消息拉取流程如下图:

4.png

 

如上图所示,用户 B 收到通知后向服务端发送拉取消息请求,该请求最终将由消息节点 1 进行处理,消息节点 1 将根据客户端传递的最后一条消息时间戳,从消息队列中返回消息列表(参考下图 )。

客户端拉取消息示例:

5.png

用户端本地最大时间为 1585224100000,从 server 端可以拉取到比这个数大的两条消息。

7.3 消息控速

服务器应对海量消息时,需要做消息的控速处理。

这是因为:在直播间聊天室中,大量用户在同一时段发送的海量消息,一般情况下内容基本相同。如果将所有消息全部分发给客户端,客户端很可能出现卡顿、消息延迟等问题,严重影响用户体验。

所以服务器对消息的上下行都做了限速处理。

消息控速原理:

6.png

具体的限速控制策略如下:

  • 1)服务器上行限速控制(丢弃)策略:针对单个聊天室的消息上行的限速控制,我们默认为 200 条 / 秒,可根据业务需要调整。达到限速后发送的消息将在聊天室服务丢弃,不再向各消息服务节点同步;
  • 2)服务器下行限速(丢弃)策略:服务端的下行限速控制,主要是根据消息环形队列的长度进行控制,达到最大值后最“老”的消息将被淘汰丢弃。

每次下发通知拉取后服务端将该用户标记为拉取中,用户实际拉取消息后移除该标记。

如果产生新消息时用户有拉取中标记:

  • 1)距设置标记时间在 2 秒内,则不会下发通知(降低客户端压力,丢弃通知未丢弃消息);
  • 2)超过 2 秒则继续下发通知(连续多次通知未拉取则触发用户踢出策略,不在此赘述)。

因此:消息是否被丢弃取决于客户端拉取速度(受客户端性能、网络影响),客户端及时拉取消息则没有被丢弃的消息。

8、直播间聊天室的消息优先级

消息控速的核心是对消息的取舍,这就需要对消息做优先级划分。

划分逻辑大致如下:

  • 1)白名单消息:这类消息最为重要,级别最高,一般系统类通知或者管理类信息会设置为白名单消息;
  • 2)高优先级消息:仅次于白名单消息,没有特殊设置过的消息都为高优先级;
  • 3)低优先级消息:最低优先级的消息,这类消息大多是一些文字类消息。

具体如何划分,应该是可以开放出方便的接口进行设置的。

服务器对三种消息执行不同的限速策略,在高并发时,低优先级消息被丢弃的概率最大。

服务器将三种消息分别存储在三个消息桶中:客户端在拉取消息时按照白名单消息 高优先级消息 低优先级消息的顺序拉取。

9、客户端针对大量消息的接收和渲染优化

9.1 消息的接收优化

在消息同步机制方面,如果直播间聊天室每收到一条消息都直接下发到客户端,无疑会给客户端带来极大性能挑战。特别是在每秒几千或上万条消息的并发场景下,持续的消息处理会占用客户端有限的资源,影响用户其它方面的互动。

考虑到以上问题,为聊天室单独设计了通知拉取机制,由服务端进行一系列分频限速聚合等控制后,再通知客户端拉取。

具体分为以下几步:

  • 1)客户端成功加入聊天室;
  • 2)服务端下发通知拉取信令;
  • 3)客户端根据本地存储的消息最大时间戳,去服务端拉取消息。

这里需要注意的是:首次加入直播间聊天室时,本地并没有有效时间戳,此时会传 0 给服务拉取最近 50 条消息并存库。后续再次拉取时才会传递数据库里存储的消息的最大时间戳,进行差量拉取。

客户端拉取到消息后:会进行排重处理,然后将排重后的数据上抛业务层,以避免上层重复显示。

另外:直播间聊天室中的消息即时性较强,直播结束或用户退出聊天室后,之前拉取的消息大部分不需要再次查看,因此在用户退出聊天室时,会清除数据库中该聊天室的所有消息,以节约存储空间。

9.2 消息的渲染优化

在消息渲染方面,客户端也通过一系列优化保证在直播间聊天室大量消息刷屏的场景下仍有不俗的表现。

以Andriod端为例,具体的措施有:

  • 1)采用 MVVM 机制:将业务处理和 UI 刷新严格区分。每收到一条消息,都在 ViewModel 的子线程将所有业务处理好,并将页面刷新需要的数据准备完毕后,才通知页面刷新;
  • 2)降低主线程负担:精确使用 LiveData 的 setValue() 和 postValue() 方法:已经在主线程的事件通过  setValue() 方式通知 View 刷新,以避免过多的 postValue() 造成主线程负担过重;
  • 3)减少非必要刷新:比如在消息列表滑动时,并不需要将接收到的新消息刷新出来,仅进行提示即可;
  • 4)识别数据的更新:通过谷歌的数据对比工具 DiffUtil 识别数据是否有更新,仅更新有变更的部分数据;
  • 5)控制全局刷新次数:尽量通过局部刷新进行 UI 更新。

通过以上机制:从压测结果看,在中端手机上,直播间聊天室中每秒 400 条消息时,消息列表仍然表现流畅,没有卡顿。

10、针对传统聊天消息外的自定义属性优化

10.1 概述

在直播间聊天室场景中,除了传统的聊天消息收发以外,业务层经常需要有自己的一些业务属性,如在语音直播聊天室场景中的主播麦位信息、角色管理等,还有狼人杀等卡牌类游戏场景中记录用户的角色和牌局状态等。

相对于传统聊天消息,自定义属性有必达和时效的要求,比如麦位、角色等信息需要实时同步给聊天室的所有成员,然后客户端再根据自定义属性刷新本地的业务。

10.2 自定义属性的存储

自定义属性是以 key 和 value 的形式进行传递和存储的。自定义属性的操作行为主要有两种:即设置和删除。

服务器存储自定义属性也分两部分:

  • 1)全量的自定义属性集合;
  • 2)自定义属性集合变更记录。

自定义属性存储结构如下图所示:

7.png

针对这两份数据,应该提供两种查询接口,分别是查询全量数据和查询增量数据。这两种接口的组合应用可以极大提升聊天室服务的属性查询响应和自定义分发能力。

10.3 自定义属性的拉取

内存中的全量数据,主要给从未拉取过自定义属性的成员使用。刚进入聊天室的成员,直接拉取全量自定义属性数据然后展示即可。

对于已经拉取过全量数据的成员来说,若每次都拉取全量数据,客户端想获得本次的修改内容,就需要比对客户端的全量自定义属性与服务器端的全量自定义属性,无论比对行为放在哪一端,都会增加一定的计算压力。

所以:为了实现增量数据的同步,构建一份属性变更记录集合十分必要。这样:大部分成员在收到自定义属性有变更来拉取时,都可以获得增量数据。

属性变更记录采用的是一个有序的 map 集合:key 为变更时间戳,value 里存着变更的类型以及自定义属性内容,这个有序的 map 提供了这段时间内所有的自定义属性的动作。

自定义属性的分发逻辑与消息一致:均为通知拉取。即客户端在收到自定义属性变更拉取的通知后,带着自己本地最大自定义属性的时间戳来拉取。比如:如果客户端传的时间戳为 4,则会拉取到时间戳为 5 和时间戳为 6 的两条记录。客户端拉取到增量内容后在本地进行回放,然后对自己本地的自定义属性进行修改和渲染。

11、多人群聊参考资料

[1] IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

[2] IM群聊消息如此复杂,如何保证不丢不重?

[3] 移动端IM中大规模群消息的推送如何保证效率、实时性?

[4] 现代IM系统中聊天消息的同步和存储方案探讨

[5] 关于IM即时通讯群聊消息的乱序问题讨论

[6] IM群聊消息的已读回执功能该怎么实现?

[7] IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?

[8] 一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践

[9] IM群聊机制,除了循环去发消息还有什么方式?如何优化?

[10] 网易云信技术分享:IM中的万人群聊技术方案实践总结

[11] 阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处

[12] IM群聊消息的已读未读功能在存储空间方面的实现思路探讨

[13] 企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等

[14] 融云IM技术分享:万人群聊消息投递方案的思考和实践

学习交流:

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK

本文已同步发布于:http://www.52im.net/thread-3835-1-1.html

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
9天前
|
人工智能 前端开发 编译器
【AI系统】LLVM 架构设计和原理
本文介绍了LLVM的诞生背景及其与GCC的区别,重点阐述了LLVM的架构特点,包括其组件独立性、中间表示(IR)的优势及整体架构。通过Clang+LLVM的实际编译案例,展示了从C代码到可执行文件的全过程,突显了LLVM在编译器领域的创新与优势。
30 3
|
12天前
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
39 1
|
5天前
|
运维 Cloud Native 持续交付
云原生技术深度探索:重塑现代IT架构的无形之力####
本文深入剖析了云原生技术的核心概念、关键技术组件及其对现代IT架构变革的深远影响。通过实例解析,揭示云原生如何促进企业实现敏捷开发、弹性伸缩与成本优化,为数字化转型提供强有力的技术支撑。不同于传统综述,本摘要直接聚焦于云原生技术的价值本质,旨在为读者构建一个宏观且具体的技术蓝图。 ####
|
6天前
|
消息中间件 运维 安全
后端开发中的微服务架构实践与挑战####
在数字化转型的浪潮中,微服务架构凭借其高度的灵活性和可扩展性,成为众多企业重构后端系统的首选方案。本文将深入探讨微服务的核心概念、设计原则、关键技术选型及在实际项目实施过程中面临的挑战与解决方案,旨在为开发者提供一套实用的微服务架构落地指南。我们将从理论框架出发,逐步深入至技术细节,最终通过案例分析,揭示如何在复杂业务场景下有效应用微服务,提升系统的整体性能与稳定性。 ####
20 1
|
7天前
|
消息中间件 运维 API
后端开发中的微服务架构实践####
本文深入探讨了微服务架构在后端开发中的应用,从其定义、优势到实际案例分析,全面解析了如何有效实施微服务以提升系统的可维护性、扩展性和灵活性。不同于传统摘要的概述性质,本摘要旨在激发读者对微服务架构深度探索的兴趣,通过提出问题而非直接给出答案的方式,引导读者深入
22 1
|
8天前
|
弹性计算 Kubernetes API
构建高效后端服务:微服务架构的深度剖析与实践####
本文深入探讨了微服务架构的核心理念、设计原则及实现策略,旨在为开发者提供一套系统化的方法论,助力其构建灵活、可扩展且易于维护的后端服务体系。通过案例分析与实战经验分享,揭示了微服务在提升开发效率、优化资源利用及增强系统稳定性方面的关键作用。文章首先概述了微服务架构的基本概念,随后详细阐述了其在后端开发中的应用优势与面临的挑战,最后结合具体实例,展示了如何从零开始规划并实施一个基于微服务的后端项目。 ####
|
5天前
|
机器学习/深度学习 人工智能 调度
【AI系统】推理引擎架构
本文详细介绍了推理引擎的基本概念、特点、技术挑战及架构设计。推理引擎作为 AI 系统中的关键组件,负责将训练好的模型部署到实际应用中,实现智能决策和自动化处理。文章首先概述了推理引擎的四大特点:轻量、通用、易用和高效,接着探讨了其面临的三大技术挑战:需求复杂性与程序大小的权衡、算力需求与资源碎片化的矛盾、执行效率与模型精度的双重要求。随后,文章深入分析了推理引擎的整体架构,包括优化阶段的模型转换工具、模型压缩、端侧学习等关键技术,以及运行阶段的调度层、执行层等核心组件。最后,通过具体的开发流程示例,展示了如何使用推理引擎进行模型的加载、配置、数据预处理、推理执行及结果后处理。
20 0
|
13天前
|
安全 测试技术 持续交付
后端开发中的微服务架构实践与挑战####
本文深入探讨了微服务架构在现代后端开发中的应用,通过一个虚构项目案例,详细阐述了从单体架构向微服务架构转型的过程、面临的挑战及解决方案。不同于常规摘要的概述性质,本文摘要旨在直接引入核心议题——如何有效实施微服务以提升系统的可扩展性、灵活性和容错能力,同时揭示转型过程中常见的技术陷阱与最佳实践策略,为读者提供实战指南。 ####
17 0
|
25天前
|
缓存 负载均衡 JavaScript
探索微服务架构下的API网关模式
【10月更文挑战第37天】在微服务架构的海洋中,API网关犹如一座灯塔,指引着服务的航向。它不仅是客户端请求的集散地,更是后端微服务的守门人。本文将深入探讨API网关的设计哲学、核心功能以及它在微服务生态中扮演的角色,同时通过实际代码示例,揭示如何实现一个高效、可靠的API网关。
|
6天前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。