网站首页 > 技术文章 正文
继续答球友提问:
(1)群离线消息是推还是拉?
(2)几万条群离线消息,怎么保证不丢失?
群离线消息,是推还是拉?
关于写扩散、读扩散的问题,之前专门撰文写过,今天不直接同步结论,重点说说设计的思考过程。
画外音:结论不如思路重要。
假如群离线是推,流程应该如何?会遇到什么问题?
先看看群离线消息的核心数据结构。
群成员表:
t_group_users(group_id, user_id)
画外音:用来描述一个群里有多少成员。
群离线消息表:
t_offine_msgs(user_id, group_id, sender_id,time, msg_id, msg_detail)
画外音:用来描述一个群成员的离线消息。
推,写扩散,存储群离线消息的过程如何?
(1)先从群成员表中,获取群里有多少个用户;
(2)从某个服务中,获取这些用户有多少个不在线;
(3)将群消息,插入到这些用户的群离线消息表;
画外音:如果要支持消息漫游,则可以省略步骤二。
此时,用户拉取离线消息的过程如何?
(1)用户登录,向server拉取离线消息;
(2)server返回并删除离线消息;
离线消息推,存在什么问题?
对于同一份群消息的内容,多个离线用户要存储很多份。假设群中有200个用户离线,离线消息则冗余了200份,这极大的增加了数据库的存储压力。
如何优化,减少消息冗余量?
为了减少离线消息的冗余度,增加一个群消息表,用来存储所有群消息的内容,离线消息表只存储用户的群离线消息msg_id,就能大大的降低数据库的冗余存储量。
群消息表:
t_group_msgs(group_id, sender_id, time, msg_id, msg_detail)
画外音:用来存储一个群中所有的消息内容。
群离线消息表,需要进行优化:
t_offine_msgs(user_id, group_id, msg_id)
画外音:优化后只存储msg_id。
这样优化后,群消息的发送和存储要做一些升级:
(1)每次发送群消息之前,先存储群消息的内容;
(2)每次存储离线消息时,只存储msg_id,而不用为每个用户存储msg_detail;
相应的,拉取离线消息也要做对应的升级:
(1)先拉取所有的离线消息msg_id;
(2)再根据msg_id拉取msg_detail;
(3)删除时,只删除自己的离线msg_id,而不删除msg_detail;
画外音:毕竟msg_detail只存储了一份,不能随便删。
上述过程,能保证离线消息的可达性么?
不能。
例如:server返回客户端离线消息之后,删除了离线消息,但客户端没有展现就奔溃了,离线消息就会丢失。
如何解决离线消息可达性呢?
很容易想到,通过ACK机制,server返回离线消息之后,不能立刻删除离线消息,而必须等客户端ACK,才能删除。
此时,离线消息拉取升级为:
(1)用户登录,向server拉取离线消息;
(2)server返回离线消息;
(3)客户端确认收到了离线消息;
(4)server再删除离线消息;
画外音:增加了3和4两个步骤。
还有一个问题,一次有几十个群,每个群有几千条离线消息,共计几万条群离线消息,消息量过大怎么办?
当然不能一次性拉取,可以:
(1)分群拉取;
(2)每个群分页拉取;
(3)拉取一页,删除一页,拉取下一页,删除下一页...
如果拉取了消息,却没来得及应用层ACK,会收到重复的消息么?
可以在客户端去重,对于重复的msg_id,对用户不展现,从而不影响用户体验。
如上所示,简单总结就是:
(1)群消息表存储消息实体msg_detail;
(2)群离线消息表,存每个用户的msg_id;
(3)分页拉取+应用层ACK,即保证性能,又保证消息可达性;
(4)客户端msg_id去重,保证用户体验;
上面讲的都是“推”模式,群离线消息的设计,真正线上应用较多的,是“拉”模式。
推模式,存在什么问题?
对于离线的每一条消息,虽然只存储了msg_id,但是每个用户的每一条离线消息都将在数据库中保存一条记录,有没有办法减少离线消息的记录数呢?
对于一个群用户,在ta登出后的离线期间内,肯定是所有的群消息都没有收到的,完全不用对所有的每一条离线消息存储一个离线msg_id,而只需要存储最近一条拉取到的离线消息的time(或者msg_id),下次登录时拉取在那之后的所有群消息即可,而完全没有必要存储每个人未拉取到的全部离线消息msg_id。
拉模式,需要对数据结构进行怎样的升级?
群成员表,增加一个属性:
t_group_users(group_id, user_id, last_ack_msg_id)
画外音:用来描述一个群里有多少成员,以及每个成员最后一条ack的群消息的msg_id(或者time)。
群消息表,不变:
t_group_msgs(group_id, sender_id, time, msg_id, msg_detail)
画外音:还是用来存储一个群中所有的消息内容。
群离线消息表:不再需要。
使用拉模式后,群消息的发送和存储也要升级:
(1)在消息msg_detail存储到群消息表后,不再需要操作离线消息表(之前需要将msg_id插入离线消息表);
(2)用户收到消息,应用层ACK后,将last_ack_msg_id更新(之前需要将msg_id从离线消息表删除);
群离线消息的拉取流程也类似:
(1)分页拉取离线消息;
(2)ACK离线消息;
(3)更新last_ack_msg_id;
总结
群消息还是非常有意思的,做个简单总结:
(1)群离线消息一般采用拉取模式,只存一份,不需要为每个用户存储离线群msg_id,只需存储一个最近ack的群消息id/time;
(2)为了保证消息可达性,在线消息和离线消息都需要ACK;
(3)离线消息过多,可以分群拉取、分页拉取等优化;
画外音:还可按需拉取,登录不拉取,点进群再拉取。
(4)如果收到重复消息,需要msg_id去重,让用户无感知;
来源: 58沈剑 架构师之路
- 上一篇: EXCEL工作表保护密码破解
- 下一篇: 「ROS自学」03.02通信方式
猜你喜欢
- 2024-09-22 群消息,究竟存1份还是多份?
- 2024-09-22 初试RocketMQ消息中间件
- 2024-09-22 PDM通知时间超长处理方法
- 2024-09-22 如何解决消息队列的延时以及过期失效问题
- 2024-09-22 深入了解 ActiveMQ
- 2024-09-22 别的老师一定不会教的,如何查看或撤消excel保护密
- 2024-09-22 群消息,究竟存一份还是多份?
- 2024-09-22 「ROS自学」03.02通信方式
- 2024-09-22 EXCEL工作表保护密码破解
- 2024-09-22 “IT小百科”之“Windows自带的服务和系统进程详解”
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)