百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

手把手教你设计一个IM单聊架构

toyiye 2024-06-21 12:28 14 浏览 0 评论

  • 单聊
    • 一. 功能点拆分
    • 二. 数据结构
    • 三. 架构层级拆分
    • 四. 推拉模式选择
    • 五. 消息流转
  • 小结


单聊

在众多的软件中,聊天功能是不可或缺的一个功能模块,或是用户和用户,或是用户和客服,都需要一个能够即时沟通的功能。

那么一个IM(InstantMessaging)的1对1聊天系统架构和存储应该如何设计呢。

下面来一步步的分析规划。

一. 功能点拆分

首先来看一个IM软件模块包括哪些基本功能

  • 会话列表(需要按照最后一条消息时间的倒序,将会话进行排列)
  • 聊天内容页(单聊双方的消息按时间顺序依次排列)
  • 未读消息计数(发送了但是没有读取的对话,需要在头像旁显示未读数字)
  • 用户头像,昵称(对话的用户资料)

根据上述功能点拆分后,可以确定下来需要哪些数据存储

  • 会话列表
  • 聊天的消息记录
  • 离线消息列表
  • 未读消息数据数量
  • 用户资料

二. 数据结构

实际进行下面几种数据结构存储时,可使用适合自己的场景的组件,例如公司自研的,或熟悉并满足场景要求的。

以下我拿redis或mysql来举例子,提供一个思路,实际生产环境还需要具体设计和选型

1. 会话列表

首先,需要为每一个会话创建一个会话Id进行标识。

再来看,会话列表的特性是新来消息的会话需要排在列表的上面,那么就可以使用一个有序集合SortedSet来存储。

结构如下:

key: prefix_xxx:{uid} value: {会话Id} score: {msgId}

key使用当前用户的uid来标识,集合中的每个item则是会话的Id,item的score为会话的最后一条消息的Id,这样根据score自动形成一个有序集合后,就能够满足我们的应用场景了。

2. 单聊消息列表

场景:聊天的消息列表,是一个按照时间顺序来排列的消息记录,并且需要可以根据offset来进行数据拉取。

同样可以使用redis的有序集合SortedSet来存储会话的消息列表,通过scan拉取消息

key: prefix_session_list:{sessionId} value: {msgId} score: {msgId}

也可以创建一个Mysql数据表来持久化存储消息记录

create table t_msg_record_list (
`id` bigint not null primary key,
`sessionId` bigint not null comment '会话Id',
`msgId` bigint not null comment '消息Id',
`isRead` tinyint not null default 0 commment '已读状态',
`recordStatus` smallint not null default 0 commment '消息状态',
`createTime` datetime not null,
key `sessionId` (`sessionId`) 
)engine=innodb;

根据会话Id分页查询时,就可以这样查询出所有msgId,再根据msgId去拉取msg的详情,组合成列表返回给客户端

SELECT msgId FROM t_msg_record_list WHERE sessionId = 1 AND recordStatus = 0 AND msgId > 1 ORDER BY id desc LIMIT 10;

3. 离线消息

离线消息可以分为「索引」和「消息id列表」两部分

离线消息索引需要记录的是,哪些用户给当前用户发送了离线消息,所以我们可以使用redis的集合Set来记录这些信息

key: prefix_xxx:{uid} value: {senderUid}

通过scan离线消息索引拿到了sendUid,再去拿这个会话的具体的离线消息id列表

然后,消息id列表使用redis的一个list链表来存储

key:prefix_offline_msg:{uid}:{senderUid} value:{msgId}

拿到所有msgId以后,去获取msg的实体详情填充即可

4. 未读计数

未读计数= 收到消息总数 - 已读数量

所以我们要存储两个已知数据便于计算出未读数量,即消息总数量和已读数量

由于对话存在双方发消息,所以分别维护对话双方的两个数据项,方便计算各自的未读数

接受消息总数量

key: prefix_session_count:{会话Id}:{uid} value: 总数量

已读数量

key: prefix_session_read_count:{会话Id}:{uid} value: 已读数量

5. 用户资料

使用mysql按需设计即可,变更保存后将数据同步到redis中使用

三. 架构层级拆分

如图所示,我们可以将架构大致分为五层,具体说明如下

1. 客户端层

我们IM服务的client肯定是有多个,web/app等,需要封装多种SDK隐藏底层细节,便于接入方接入。

2. 连接层

即时通讯需要客户端和服务端之间建立一个长链接,一方面维护用户的在线状态,另一方面便于复用连接进行消息的收发。

而维护连接这个动作,它的独立性很强,不需要与业务逻辑耦合,所以我们把链接层单独拆分出来一个。

这样在业务逻辑迭代上线时,业务层进行滚动上线也不会导致用户的链接断开。

连接协议

至于连接协议的选择,有如下几种方式

  1. 基于tcp链接,自定义传输协议(开发成本高,需要有一定条件)
  2. websocket
  3. http chunk (不建议使用,http工作在7层上,且只能服务端单向的向客户端传输数据,心跳连接不好维护)

这里推荐优先使用四层的协议来进行长链接的维护。

因为长链接集群的前方要做负载均衡,使用七层的协议,客户端要先和负载均衡机器建立链接,然后负载均衡机器再和业务层集群交互。

这样在连接数很大的时候,负载均衡的机器容易成为瓶颈。四层的负载均衡可以直接通过修改目标机器ip prot的方式来进行转发,不需要client和负载均衡机器建链接

3. 业务层

业务层可以分为「长链接业务层 」和「短链接业务层

具体两者的功能拆分,可根据业务实际情况设计

  • 长链接业务层: 负责会话相关的业务逻辑,比如收发消息/拉取会话列表/未读计数push等业务
  • 短链接业务层: 负责一些临时接口请求,比如用户资料拉取/资料变更等类似业务

两种业务层都通过调用服务层来进行数据读取和写入等擦欧总

4. 服务层

这层属于微服务,来为上层业务层提供基础服务能力,例如敏感消息过滤/会话列表数据读写/消息的落地和发送等功能。

5. 数据层

为上层的服务层来提供数据的实际落地写入,可以使用mysql,redis或其他sql/nosql数据库。

四. 推拉模式选择

那么在消息的发送上,我们应该选用推模式,还是拉模式,抑或是推拉结合呢?

1. 纯推模式

首先,我们假设使用纯推模式 ,来看会存在什么样的问题

场景1: 新设备登陆初始化

用户新登陆一台设备的时候,如果消息记录全都是空的,体验会很不好。

那么就需要服务端推送全量 的消息记录到客户端,历史消息量大的时候,非常浪费服务端资源和带宽。

场景2: 设备间切换

tips:设备A和B都非第一次登陆

如图所示,流程如下

  1. 用户1在设备A上登陆,收到了用户2的消息1和2,push到了设备A上。
  2. 用户1退出了设备A,用户2又给他发送了消息3和4
  3. 用户1登陆了设备B,服务端push消息3和4到了设备B

但是此时,设备B缺少了消息1和2,用户再登陆回设备A的话又缺少了消息3和4,这也就产生了「消息空洞

2. 纯拉模式

然后,我们假设使用纯拉模式 ,来看会存在哪些问题

场景1: 收新消息

纯拉模式下,客户端需要和服务端进行一个长轮询,来定时检查是否存在新消息,并进行消息拉取。

这样轮询的时间间隔需要很难确定合适,间隔大了消息不实时,间隔小了无疑对服务器会产生很大的压力,无法支撑大量的在线用户进行聊天。

总结

由于推拉模式分别适用于业务中的不同场景需要,所以我们要使用推拉结合的方式来做。

拉模式适合的场景如下:

  1. 设备初始化时:先拉取会话列表,在根据会话的列表来为每个会话拉取一定的消息记录。可以通过控制拉取的数据量,减轻服务端压力。
  2. 历史聊天记录:按需拉取一定条数的记录,用户向上翻取记录再拉取固定条数的记录,直到翻到没有记录(就是翻页)。

推模式适合的场景如下:

  1. 用户实时接收消息
  2. 用户在线,有未读消息做通知栏push时

五. 消息流转

上面确定好推拉模式后,我们来看发消息和收消息都有哪些业务逻辑执行。

发消息

如上图所示,大致可分为三步

1. 消息过滤

首先用户的消息通过客户端的SDK发送出来,通过长链接到达了「逻辑层」,逻辑层接收到该请求后,可以根据定义的拦截过滤规则调用「服务层」的服务接口,来对消息进行处理;

2. 消息补充

处理通过后,来对消息的发送方资料进行填充,简单来说就是senderId标识,接收方接收消息时能够填充到对应的会话中。

3. 派发任务

消息实体处理完成后,将该消息push到「服务层」的「异步任务队列」服务中。

异步队列任务 主要需要做以下四个方面的操作

  1. 更新存储端的「聊天记录」
  2. 更新会话的「消息总数量」,用来计算未读计数
  3. 根据接收方的在线状态来判断,是直接进行push,还是存入到离线列表中,等待用户上线后再进行消息拉取
  4. 更新「会话列表」的score值

具体异步队列还可以细化拆分,例如

实时任务队列

延时任务队列

失败重试队列 分别启动不同的线程池来消费任务,按需分配线程数处理

收消息

收消息主要有以下几个场景需要处理

  1. 客户端需要将消息append到聊天列表中,并在会话列表中将该会话增加未读消息标识。
  2. 如果接收方打开了开聊天窗口,客户端会发送一个消息的ACK给服务端,来标记该消息已读。
  3. 服务端收到已读ACK后需要更新「已读计数」相关数据项
  4. 如果是拉取离线消息,服务端还需要更新「离线消息」相关数据项

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

项目地址:https://github.com/YunaiV/ruoyi-vue-pro

视频教程:https://doc.iocoder.cn/video/

小结

本文从五个方面来对单聊的IM架构进行了设计分析

  1. 业务功能拆分
  2. 数据结构设计
  3. 系统结构设计
  4. 推拉模式选择
  5. 消息流转分析 讲了基础的结构有哪些,数据结构有哪些要求,以及消息流传的过程是什么样的。

对im单聊场景的开发框架有了大体的一个认识,但是实际落地的时候还有很多细节需要去实现。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码