当前位置: 首页 > news >正文

IM系统群消息推送方案

常见的群消息推送流程

场景:a、b、c 位于同一个群中。a、b 在线,c 离线
常见的消息推送流程如下:

  1. a 发送一条消息
  2. 服务端接收消息,查询在线用户,将消息转发给在线用户 b
  3. 服务端将消息存储到 c 的离线库

上述这种推送模型是“写扩散”,如果群里有 200 个用户,最坏的情况一条消息需要存储199次,消息扩散系数为 n-1

存在的问题

同一份消息存储了多份,极大的增加了数据库的存储压力。

优化1:减少存储量

存储离线消息时,可以只存储消息的 seq_id (唯一 id)。这样消息本体只存储一份,可以大幅度的降低数据库的冗余数据。

假设 a 在群里连续发了 100 条消息,那么 c 的离线消息库里面将会有对应 100 条记录。

有必要吗?

聪明的你肯定想到了我们可以只存储 c 的last_ack_seq_id(最后一次收到的消息的 seq_id)。因为 c 离线后的所有消息都是未收到的。在 c 上线时,拉取群内 last_ack_seq_id 后的消息即可。

现在的流程如下:

  1. a 发送一条消息
  2. 服务器收到消息,持久化消息
  3. 服务端查询在线用户,将消息推送给在线用户 b,并更新 b 的 last_ack_seq_id
  4. c 上线后,拉取群内 last_ack_seq_id后的消息

存在的问题

虽然 websocket 是基于 tcp 实现的,但是仍然需要我们在应用层引入相关机制,确保消息可靠传输。因为 tcp 的可靠传输针对的是传输层,对应用层不负责,例如:客户端成功接收消息,但是消息处理失败。

优化2:可靠传输

参考 tcp 的可靠传输,我们可以引入 ack 应答、超时重传、去重机制。

首先我们需要确保服务端一定收到消息。

  1. 当客户发送消息时为每条消息生成唯一 seq_id,并开启定时任务。
  2. 当服务端收到 a 发送的群消息将其持久化后,回复 ack 给 a。
  3. 当定时任务超时仍为收到 ack 时重新发送消息。

其次我们需要确保客户端一定收到消息。

  1. 服务端向在线用户 b 发送消息,携带消息 seq_id,并开启定时任务
  2. 客户收到消息,返回 ack 给服务端
  3. 服务端修改 last_ack_seq_id
  4. 当定时任务超时仍未收到 ack 时重新发送消息

至于离线用户,不用管他

那我问你,这样真的就没问题了吗?

存在的问题

客户端向服务端发送消息的过程没有问题,问题在服务端修改 last_ack_seq_id
例如:

  1. 在同一个群内,服务端先向 b 发送消息 1
  2. 服务端又向 b 发送消息 2
  3. b 返回了 消息 2 的 ack
  4. 服务端修改 last_ack_seq_id 为消息2 的 seq_id

由于网络的原因,无法保证服务端接收 ack 的顺序,我们可以使用 redis 解决这个问题。我们在 redis 中定义两个类型数据结构:list,hash。list 中维护着服务器在当前群组中推送消息的顺序。hash 中维护着消息的状态,是否 ack。发送消息流程如下:

  1. 服务端在 redis 的 list 中添加消息1,再在 hash 把消息1 的状态设为 pending,发送消息1
  2. 服务端在 redis 的 list 中添加消息2,再在 hash 把消息2 的状态设为 pending,发送消息2
  3. b 返回了消息2 的 ack
  4. 服务端将 hash 中的消息2 状态设为 acknowledged,判断队头元素是否为acknowledged状态,此时队头 member 为消息1,消息1的状态为pending,跳出循环
  5. b 返回了消息1 的 ack
  6. 服务端将 hash 中的消息1 状态设置为 acknowledged,断队头元素是否为acknowledged状态,此时队头 member 为消息1,消息1的状态为acknowledged,弹出消息1,重复操作,直到队头 member 状态为 pending
  7. 服务端更新 MySQL 中群成员关系表中的的 last_act_seq_id 字段

群消息推送流程图

在这里插入图片描述

beautiful~

相关文章:

  • 发那科机器人4(编程实例)
  • 死锁的形成
  • 嵌入式开发学习(第二阶段 C语言基础)
  • 学习黑客威胁情报(Threat Intelligence)
  • TensorFlow深度学习实战(15)——编码器-解码器架构
  • docker 日志暴露方案 (带权限 还 免费 版本)
  • 阿里云 SLS 多云日志接入最佳实践:链路、成本与高可用性优化
  • c/c++的Libevent 和OpenSSL构建HTTPS客户端详解(附带源码)
  • Python毕业设计219—基于python+Django+vue的房屋租赁系统(源代码+数据库+万字论文)
  • 如何制作网站?制作网站的流程。
  • C++ 观察者模式详解
  • k8s之ingress
  • 电路研究9.3.4——合宙Air780EP中的AT开发指南:HTTPS示例
  • 具身智能数据集解析
  • n8n系列(4):生产环境最佳实践
  • 数据库基础:概念、原理与实战示例
  • 云轴科技ZStack入选赛迪顾问2025AI Infra平台市场发展报告代表厂商
  • 万兴PDF-PDFelement v11.4.13.3417
  • 对遗传算法思想的理解与实例详解
  • odoo-049 Pycharm 中 git stash 后有pyc 文件,如何删除pyc文件
  • 江苏省人社厅党组书记、厅长王斌接受审查调查
  • 105岁八路军老战士、抗美援朝老战士谭克煜逝世
  • 马上评|让“贾宝玉是长子长孙”争议回归理性讨论
  • 谜语的强制力:弗洛伊德与俄狄浦斯
  • 纪念|“补白大王”郑逸梅,从藏扇看其眼光品味
  • 巴基斯坦所有主要城市宣布进入紧急状态,学校和教育机构停课