写点什么

一套分布式 IM 即时通讯系统的技术选型和架构设计

作者:JackJiang
  • 2023-12-21
    江苏
  • 本文字数:5847 字

    阅读完需:约 19 分钟

一套分布式IM即时通讯系统的技术选型和架构设计

本文由冰河分享,作者博客 binghe.gitcode.host,原题“这套分布式 IM 即时通讯系统如何写到简历上?我给你整理好了!”,本文有修订和改动。

1、引言

分布式 IM 即时通讯系统本质上就是对线上聊天和用户的管理。

针对聊天本身来说,最核心的需求就是:发送文字、图片、文件、语音、视频、消息缓存、消息存储、消息未读、已读、撤回,离线消息、历史消息、单聊、群聊,多端同步,以及其他一些需求。

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

为了更好的理解分布式 IM 即时通讯系统的设计,我站在架构师的角度,在充分了解系统需求、业务流程和技术流程后,从全局视角为系统设定方案目标,对技术方案进行选型,对系统进行总体架构设计和分层架构设计,并梳理清楚发送消息的交互链路、单聊和群聊的交互链路。希望对你有帮助。

 

技术交流:

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

- 开源 IM 框架源码:https://github.com/JackJiang2011/MobileIMSDK备用地址点此

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

2、方案目标

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

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

具体是:

  • 1)业务目标:满足需求设计篇章中的各类需求场景;

  • 2)技术目标:支持无限扩容,百万用户同时在线聊天;

  • 3)架构目标:高并发、高性能、高可用、可监控、可预警、可伸缩,支持无限扩展。

3、技术选型

在技术选型上,除了采用 SpringBoot 等基础框架外,也会采用容器化方案。

同时,考虑到为了尽量降低技术门槛,在整个分布式 IM 即时通讯系统的技术选型中,主要采用市面上比较流行的技术框架和方案。

具体选型如下所示:

  • 1)开发框架:SpringBoot、SpringCloud、SpringCloud Alibaba、Dubbo;

  • 2)缓存:Redis 分布式缓存+Guava 本地缓存;

  • 3)数据库:MySQL、TiDB、HBase;

  • 4)流量网关:OpenResty+Lua;

  • 5)业务网关:SpringCloud Gateway + Sentinel;

  • 6)持久层框架:MyBatis、Mybatis-Plus;

  • 7)服务配置、服务注册与发现:Nacos;

  • 8)消息中间件:RocketMQ;

  • 9)网络通信:Netty

  • 10)文件存储:Minio;

  • 11)日志可视化治理:ELK;

  • 12)容器化管理:Swarm、Portainer;

  • 13)监控:Prometheus、Grafana;

  • 14)前端:Vue;

  • 15)单元测试:Junit;

  • 16)基准测试:JMH;

  • 17)压力测试:JMeter。

4、初步架构设计

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

 

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

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

5、架构设计优化

既然上节图中所示的架构设计存在性能瓶颈,那我们如何进行优化呢?

为此我们在前图基础上进行了优化,优化后的架构如下图所示。

对比两图可以看出,在屏蔽掉技术实现细节的前提下,我们将对业务的校验和流量管控进行前置化,放大 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 和取模运算,使其尽量均匀分布到集群中的每一个服务实例上。

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

6、容器化架构设计

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

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

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

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

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

7、DDD 分层业务架构设计

在分布式 IM 即时通讯系统中,不管是大后端平台,还是 IM 即时通讯服务,我们都会对业务层的代码采用分层业务架构。

这里,可以借鉴 DDD 的分层架构思想,将代码总体上分成展示层、应用层、领域层和基础设施层四个层次。

但是,考虑到分布式 IM 即时通讯系统的特殊性,又不会严格按照 DDD 的原则来设计代码分层,具体按照如下图所示。

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

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

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

3)领域层:领域层,也叫做 Domain 层,领域层可以说是 DDD 设计的精髓所在,它是将业务系统中相对不变的部分抽象出来封装成领域模型。在分布式 IM 即时通讯系统的设计中,领域层基本不会依赖其他层,也不会依赖基础设施层,这里是与 DDD 设计存在区别的地方。

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

8、总体 IM 消息交互链路

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

可以看到:用户在分布式 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 的消息;

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

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

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

9、IM 单聊交互链路

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

例如:用户 A 给用户 B 发送消息时,用户 B 可能不在线。

此时,我们就需要将用户 A 向用户 B 发送的消息存储起来。

其实,在我们实现的分布式 IM 即时通讯系统中,无论把用户 B 是否在线,都会存储消息记录。当用户 B 登录系统后,将消息同步给用户 B,如下图所示。

可以看到,用户 A 向用户 B 发送消息时:

  • 1)如果用户 B 在线,就可以按照发送消息的交互链路向用户 B 发送消息了;

  • 2)如果用户 B 不在线,此时就无法向用户 B 正常推送消息。当用户 B 登录分布式 IM 即时通讯系统后,就会调用后端平台的接口拉取所有未读消息,并通过用户 B 在线流程向用户 B 推送消息。

10、IM 群聊交互链路

群聊就是在分布式 IM 即时通讯系统中,多个用户在同一个群组中进行聊天。

此时在发送消息时,我们可以通过群组 ID 找出群内所有在线的用户,将消息即时发送给在线的用户。

那些未在线的用户就按照单聊未在线的用户进行处理,如下图所示。

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

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

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

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

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

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

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

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

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

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

好了,看到这里,你明白如何设计一个高度可扩展的分布式 IM 即时通讯系统了吗?

11、相关资料

[1] 浅谈IM系统的架构设计

[2] 简述移动端IM开发的那些坑:架构设计、通信协议和客户端

[3] 一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

[4] 一套原创分布式即时通讯(IM)系统理论架构方案

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

[6] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[7] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[8] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

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

[10] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制

[11] 阿里IM技术分享(三):闲鱼亿级IM消息系统的架构演进之路

[12] 基于实践:一套百万消息量小规模IM系统技术要点总结

[13] 跟着源码学IM(十):基于Netty,搭建高性能IM集群(含技术思路+源码)

[14] 一套十万级TPS的IM综合消息系统的架构实践与思考

[15] 得物从0到1自研客服IM系统的技术实践之路

[16] 海量用户IM聊天室的架构设计与实践

[17] 史上最通俗Netty入门长文:基本介绍、环境搭建、动手实战

[18] 新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析

[19] 写给初学者:Java高性能NIO框架Netty的学习方法和进阶策略

[20] 手把手教你用Netty实现网络通信程序的心跳机制、断线重连机制

[21] 史上最强Java NIO入门:担心从入门到放弃的,请读这篇!

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

用户头像

JackJiang

关注

还未添加个人签名 2019-08-26 加入

开源IM框架MobileIMSDK、BeautyEye的作者。

评论

发布
暂无评论
一套分布式IM即时通讯系统的技术选型和架构设计_网络编程_JackJiang_InfoQ写作社区