4

互联网工程实践:这套分布式IM即时通讯系统,是如何做到弹性扩缩容的?

 7 months ago
source link: https://www.51cto.com/article/779945.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

互联网工程实践:这套分布式IM即时通讯系统,是如何做到弹性扩缩容的?

作者:冰河 2024-01-22 08:01:17
当IM即时通讯服务接收到消息后,此时用户掉线,或者用户不在线,向用户推送消息就会失败,或者未查询到用户与IM即时通讯服务建立的连接,就不会向用户推送消息。

分布式IM即时通讯系统本质上就是对线上聊天和用户的管理,针对聊天本身来说,最核心的需求就是:发送文字、图片、文件、语音、视频、消息缓存、消息存储、消息未读、已读、撤回,离线消息、历史消息、单聊、群聊,多端同步,以及其他一些需求。

对用户管理来说,存在的需求包含:添加好友、查看好友列表、删除好友、查看好友信息、创建群聊、加入群聊、查看群成员信息、退出群聊、修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注以及其他用户相关的需求等。

注:拿小本子记录下,后续可以写到简历上的整合了OpenAI大模型的分布式IM即时通讯系统,从此,简历上又多了一个可以拿的出手的高并发、高性能、高可用、可监控、可预警、可伸缩,支持高可扩展的真实业务场景项目。

为了能够让小伙伴们更好的理解分布式IM即时通讯系统的设计,我们站在架构师的角度,在充分了解系统需求,业务流程和技术流程后,从全局视角为系统设定方案目标,对技术方案进行选型,对系统进行总体架构设计和分层架构设计,并梳理清楚发送消息的交互链路、单聊和群聊的交互链路。以方便各位小伙伴将分布式IM即时通讯系统写到自己的简历中,增强自己的竞争力。

二、方案目标

在进行技术选型与总体架构设计之前,需要明确一个事项,就是系统无论采用哪种方案,采用哪种架构设计都需要明确这种方案的业务目标、技术目标和架构目标,并在研发过程中不断评估系统的总体性能表现,发现系统瓶颈并不断进行优化。

总体上,我们搭建和开发的分布式IM即时通讯系统,需要满足如下方案目标。

  • 业务目标:满足需求设计篇章中的各类需求场景。
  • 技术目标:支持无限扩容,百万用户同时在线聊天。
  • 架构目标:高并发、高性能、高可用、可监控、可预警、可伸缩,支持无限扩展。

三、技术选型

在技术选型上,除了采用SpringBoot等基础框架外,也会采用容器化方案。同时,考虑到为了尽量降低技术门槛,在整个分布式IM即时通讯系统的技术选型中,主要采用市面上比较流行的技术框架和方案,具体选型如下所示。

  • 开发框架:SpringBoot、SpringCloud、SpringCloud Alibaba、Dubbo。
  • 缓存:Redis分布式缓存+Guava本地缓存。
  • 数据库:MySQL、TiDB、HBase。
  • 流量网关:OpenResty+Lua。
  • 业务网关:SpringCloud Gateway + Sentinel。
  • 持久层框架:MyBatis、Mybatis-Plus。
  • 服务配置、服务注册与发现:Nacos。
  • 消息中间件:RocketMQ。
  • 网络通信:Netty。
  • 文件存储:Minio。
  • 日志可视化治理:ELK。
  • 容器化管理:Swarm、Portainer。
  • 监控:Prometheus、Grafana。
  • 前端:Vue。
  • 单元测试:Junit。
  • 基准测试:JMH。
  • 压力测试:JMeter。

四、系统初步架构设计

对于IM即时通讯系统来说,涵盖了即时通讯后端服务、大后端平台、SDK接入服务、OpenAI接入服务、大前端UI,我相信不少小伙伴多多少少能够画出IM即时通讯系统的架构图,大致如图1-1所示。

图片

图片

其实,这种这种架构设计也比较常见,在这种架构设计中,Kong/Openresty/Nginx只做负载均衡和反向代理,研发人员更多的是关注业务层和基础层的开发,流量比较小时,这种架构设计一般不会有什么问题。但是一旦流量比较大,用户调用后端平台的接口发送消息时,即时通讯SDK同步调用即时通讯服务的接口就会出现性能问题。

因为每个终端同时只能与一个IM即时通讯服务实例建立连接,如果大量的用户终端恰好都与一个IM即时通讯服务建立连接,那即时通讯SDK频繁同步调用同一个IM即时通讯服务的接口就会出现性能瓶颈。此时,出现性能瓶颈时,不仅仅会影响到IM即时通讯服务,也会对后端平台接收请求的业务造成一定的影响。

五、系统架构设计优化

既然图1-1所示的架构设计存在性能瓶颈,那我们如何进行优化呢?为此我们在如1-1的基础上进行了优化,优化后的架构如图1-2所示。

图片

图片

对比图1-1和图1-2可以看出,在屏蔽掉技术实现细节的前提下,我们将对业务的校验和流量管控进行前置化,放大Kong/OpenResty/Nginx的职责,使得这些软件不仅具备反向代理和负载均衡的功能,还能实现限流、黑白名单、流量管控、业务校验等功能。

也就是说,在这种架构模式下,我们充分发挥了整个分布式IM即时通讯系统的入口职责,充分利用Kong/OpenResty/Nginx的高并发、高吞吐量的能力,尽量将大部分无效请求挡在整个系统之外。例如,用户在没登录系统的前提下,就尝试调用发送消息、添加好友、添加群组等等接口。这样会大大减轻后台平台的业务压力。

除了在Kong/OpenResty/Nginx中实现限流、黑白名单、流量管控、业务校验等功能外,我们还引入了业务网关集群,实现限流、降级、熔断、流控、校验、鉴权等功能,进一步保证下游系统的稳定性和安全。

为了解决大量用户终端恰好连接到同一个IM即时通讯服务实例,IM即时通讯SDK频繁调用同一个IM即时通讯服务实例的接口造成的性能问题。我们在IM即时通讯服务SDK与IM即时通讯服务之间引入了RocketMQ集群。

IM即时通讯服务集群中的每一个IM即时通讯服务实例在集群中都有一个唯一的ID,并且每个IM即时通讯服务实例在启动后,只会监听RocketMQ中与自身ID相关的Topic。这样每个IM即时通讯服务只会收到与自身ID相关的Topic中的消息,不会接收所有的消息。

当用户登录系统后,就会与IM即时通讯服务建立长连接,并且会以用户ID和终端为Key,以IM即时通讯服务的ID为value,将其存储到分布式缓存中。同时,会以用户ID和终端为Key,以用户终端与IM即时通讯服务建立的长连接为value,将其存储到IM即时通讯服务本地内存中。

当用户调用后端平台的接口发消息时,会带上目标用户的ID,并且在IM即时通讯SDK中会指定用户登录的终端设备,最终会通过IM即时通讯SDK向RocketMQ发送消息。

此时IM即时通讯SDK会根据目标用户ID和终端从分布式缓存中获取目标用户连接的IM即时通讯服务的ID,并向此ID相关的Topic发送消息。此时与目标用户建立长连接的IM即时通讯服务就会接收到RocketMQ中的消息,随后根据用户ID和终端从本地缓存中获取到与用户终端建立的长连接,并基于此长连接向用户推送消息。

另外,在实际实现中,为了避免大量用户同时只连接IM即时通讯服务集群中的某一个服务实例,会对用户连接的IP、浏览器指纹、手机设备等做Hash和取模运算,使其尽量均匀分布到集群中的每一个服务实例上。

那么问题来了:这种架构设计还有进一步优化的空间吗?

六、容器化架构设计

为进一步增强分布式IM即时通讯系统的性能、可用性和弹性伸缩能力,我们可以对分布式IM即时通讯系统进行容器化架构设计,如图1-3所示。

图片

图片

可以看到,我们对分布式IM即时通讯系统的架构设计进行了进一步优化,采用了容器化架构设计。在原有架构的基础上,我们进行了如下改进和优化。

(1)基础支撑服务

基础支撑服务会由各种基础中间件、数据存储服务、以及监控服务实现,包含:MySQL数据库、TiDB数据库、HBase、Redis缓存、RocketMQ消息队列、Prometheus监控和Portainer容器管理等基础中间件实现,基础支撑服务会对整个分布式IM即时通讯系统提供最基础的数据、传输、监控和容器管理等服务。

(2)容器化

在容器化层面,会通过Docker、Swarm和Portainer实现,其中,会基于Swarm和Portainer对容器化进行管理。

(3)其他基础性功能实现

除了上述分层架构外,对于建设分布式IM即时通讯系统来说,还要考虑异常监控、服务注册与发现、可视化、服务降级与兜底数据、服务限流、服务容灾、容量规划与扩缩容和全链路压测等。

七、DDD分层业务架构设计

在分布式IM即时通讯系统中,不管是大后端平台,还是IM即时通讯服务,我们都会对业务层的代码采用分层业务架构,这里,可以借鉴DDD的分层架构思想,将代码总体上分成展示层、应用层、领域层和基础设施层四个层次,但是,考虑到分布式IM即时通讯系统的特殊性,又不会严格按照DDD的原则来设计代码分层,具体按照如图1-4所示。

图片

图片

可以看到,分布式IM即时通讯系统会借鉴DDD的设计思想,但是不会完全按照DDD的方式进行设计。

(1)展示层

展示层,也叫做用户UI层,是DDD设计的最上层,对外提供API接口,接收客户端请求,解析参数,返回结果数据,并对异常进行处理。

(2)应用层

应用层,也叫做Application层,应用层主要处理容易变化的业务场景,可对相关的事件、调度和其他聚合操作进行相关的处理。

(3)领域层

领域层,也叫做Domain层,领域层可以说是DDD设计的精髓所在,它是将业务系统中相对不变的部分抽象出来封装成领域模型。

在分布式IM即时通讯系统的设计中,领域层基本不会依赖其他层,也不会依赖基础设施层,这里是与DDD设计存在区别的地方。

(4)基础设施层

基础设施层,也叫做Infrastructure层,基础设施层会对其他各层提供通用的基础能力,在分布式IM即时通讯系统中,就包括了缓存、通用工具类、消息、系统的持久化机制等。

八、发送消息交互链路

在分布式IM即时通讯系统中,我们忽略掉其他一些细节信息,重点关注下发送消息的交互链路逻辑。不管是单聊还是群聊,最终都需要通过IM即时通讯服务将消息推送给用户的终端。此时发送消息的流程如图1-5所示。

图片

图片

可以看到,用户在分布式IM即时通讯系统发送消息时,不管是单聊还是群聊,最终的消息都会推送到用户登录的终端设备上。假设此时用户A给用户B发送消息,或者用户A和用户B在同一个群组,用户A向群组发送消息,用户B接收消息的主要流程如下。

(1)用户A调用后端平台的接口向用户B发送消息,并且发送的消息中会带有用户B的ID以及终端信息。

(2)后端平台将消息缓存起来,并且会将消息异步写入消息库。

(3)后端平台从Redis中获取用户B连接的IM即时通讯服务的ID。

(4)后端平台获取到用户B连接的IM即时通讯服务的ID后,会向RocketMQ中用户B连接的IM即时通讯服务ID对应的Topic发送消息。

(5)IM即时通讯服务会监听自身服务ID对应的RocketMQ中Topic的消息,此时,用户B连接的IM即时通讯服务会接收到消息。

(6)IM即时通讯服务接收到消息后,会根据用户B的ID以及终端信息从缓存中获取用户B与IM即时通讯服务建立的连接,并且通过这个连接向用户B推送消息。

要实现如上发送消息的流程,前提是要满足如下条件。

(1)后端平台满足分布式条件,可随时横向扩展。

(2)IM即时通讯服务满足分布式条件,可随时横向扩展。

(3)每个启动的IM即时通讯服务实例在集群中都有一个唯一的ID。

(4)每个IM即时通讯服务,都只监听自身ID对应的RocketMQ中Topic的消息。

(4)用户登录分布式IM即时通讯系统后,会与IM即时通讯服务建立长连接,并且会根据用户ID和所在的终端缓存长连接,同时会根据用户ID和所在的终端将连接的IM即时通讯服务的ID缓存到Redis。

(6)用户发送消息时,会根据目标用户的ID和终端从Redis中获取IM即时通讯服务的ID,进而向当前IM即时通讯服务的ID对应的RocketMQ的Topic发送消息。

(7)对应的IM即时通讯服务监听并接收到RocketMQ消息后,会根据目标用户的ID和终端从缓存中获取到用户的连接信息,向目标用户推送消息。

九、单聊交互链路

单聊就是在分布式IM即时通讯系统中,一个用户直接与另外一个用户聊天,也就是一对一的聊天。在这种场景下,很有可能单聊的两个用户中,出现用户不在线的情况。

例如,用户A给用户B发送消息时,用户B可能不在线。此时,我们就需要将用户A向用户B发送的消息存储起来。其实,在我们实现的分布式IM即时通讯系统中,无论把用户B是否在线,都会存储消息记录。当用户B登录系统后,将消息同步给用户B,如图1-6所示。

图片

图片

可以看到,用户A向用户B发送消息时,如果用户B在线,就可以按照发送消息的交互链路向用户B发送消息了。如果用户B不在线,此时就无法向用户B正常推送消息。当用户B登录分布式IM即时通讯系统后,就会调用后端平台的接口拉取所有未读消息,并通过用户B在线流程向用户B推送消息。

十、群聊交互链路

群聊就是在分布式IM即时通讯系统中,多个用户在同一个群组中进行聊天,此时在发送消息时,我们可以通过群组ID找出群内所有在线的用户,将消息即时发送给在线的用户。那些未在线的用户就按照单聊未在线的用户进行处理,如图1-7所示。

图片

图片

可以看到,群聊的交互链路流程如下所示。

(1)用户调用后端平台的接口向群组发送消息。

(2)后端平台将消息缓存并异步写入消息库。

(3)由于是向群组发送消息,群里有多个用户,此时就会从Redis中获取所有用户连接的IM即时通讯服务ID列表。

(4)对用户按照服务ID分组,将相同服务ID下的用户分在同一个逻辑分组里,方便后续推送消息,并且会记录未在线的用户列表。

(5)循环向每个服务ID对应的RocketMQ中的Topic发送消息。

(6)广播处理未在线用户的未读消息ID。

(7)IM即时通讯服务会监听自身服务ID对应的Topic,会随时接收推送到自身服务的消息。

(8)当IM即时通讯服务接收到消息后,此时用户掉线,或者用户不在线,向用户推送消息就会失败,或者未查询到用户与IM即时通讯服务建立的连接,就不会向用户推送消息。

(9)当用户登录分布式IM即时通讯系统后,会从后端平台拉取历史(离线)消息,并通过用户在线的流程,向用户推送消息。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK