当前位置: 首页 > 科技观察

几万条群线下消息,如何高效拉取,会不会丢失?

时间:2023-03-16 15:24:57 科技观察

继续回答球友提问:群下线消息是push还是pull?数以万计的群离线消息,如何保证不丢失?分组离线消息,推送还是拉取?关于写作扩散和阅读扩散的问题,之前写过一篇专门的文章,今天不直接同步结论,而是重点说说设计的思考过程。画外音:结论不如想法重要。如果群下线是push的话,流程应该是怎样的?会遇到什么问题?首先我们来看一下群下线消息的核心数据结构。群成员表:t_group_users(group_id,user_id)画外音:用来描述一个群有多少成员。群组离线消息表:t_offine_msgs(user_id,group_id,sender_id,time,msg_id,msg_detail)画外音:用于描述群组成员的离线消息。群线下消息推送、写入、传播、存储的流程是怎样的?首先从群组成员表中获取群组中有多少用户;从某项服务中获取这些用户中有多少人处于离线状态;将群组消息插入到这些用户的群组离线消息表中;画外音:如果要支持消息漫游,可以省略第2步。此时,用户拉取离线消息的流程是怎样的?用户登录,从服务器拉取离线消息;服务器返回并删除离线消息;推送离线消息,有什么问题?保存多份。假设群内有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。本次优化后,群消息的发送和存储需要升级:每次发送群消息前,存储群消息的内容;每次存储离线消息时,只存储msg_id,而不是每个用户的msg_detail;是的,拉取离线消息需要相应升级:先拉取所有离线消息msg_id;然后根据msg_id拉取msg_detail;删除时只删除自己的离线msg_id,不删除msg_detail;一旦有了副本,就不能随便删除。以上流程能否保证离线消息的可达性?没有。例如:服务端返回客户端的离线消息后,删除了离线消息,但是客户端没有显示就崩溃了,离线消息就会丢失。如何解决离线消息的无障碍?很容易想到,通过ACK机制,服务端返回离线消息后,离线消息并不能立即删除,必须等待客户端ACK才能删除。此时离线消息拉取升级为:用户登录,从服务器拉取离线消息;服务器返回离线消息;客户端确认收到离线消息;服务器删除离线消息;画外音:增加了两个步骤3和4。还有一个问题。一次有几十个群,每个群都有几千条离线消息。总共有数万条群组离线消息。消息量过大怎么办?当然,我不能一下子把它们都拉出来。您可以:按组拉取;每个组都按页面拉;拉取一页,删除一页,拉取下一页,删除下一??页……如果消息拉取了,但是没有时间应用层ACK,会不会收到重复的消息?可以在客户端去重,重复的msg_id不会显示给用户,以免影响用户体验。如上图,简单概括就是:群消息表存储的是消息实体msg_detail;群下线消息表存储每个用户的msg_id;分页拉取+应用层ACK,兼顾性能和消息可达性;clientmsg_id去除重复,保证用户体验;以上都是关于“推”模式,群线下消息的设计,以及线上实际使用较多的“拉”模式。推送模式,有什么问题?对于每条离线消息,虽然只存储了msg_id,但是每个用户的每条离线消息都会在数据库中保存一条记录。有什么办法可以减少离线消息记录的数量吗?对于群组用户,在注销后的离线期间内,不得接收到所有群组消息。根本不需要为每条离线消息都存储一个离线msg_id,只需要存储最新取到的一条即可。离线消息的时间(或msg_id),下次登录时可以拉取之后所有的群消息,不需要存储所有没有拉取的离线消息msg_id。拉模式,如何升级数据结构?给群成员表增加一个属性:t_group_users(group_id,user_id,last_ack_msg_id)画外音:用来描述一个群有多少成员,以及每个成员最后一次ack的群消息的msg_id(或时间).群消息表,不变:t_group_msgs(group_id,sender_id,time,msg_id,msg_detail)画外音:仍然用于存放一个群内所有的消息内容。群组离线消息表:不再需要。使用pull模式后,群消息的发送和存储也要升级:消息msg_detail存入群消息表后,不再需要操作离线消息表(需要在群消息表中插入msg_id)离线消息表之前);当用户收到消息后,应用层ACK后,更新last_ack_msg_id(之前需要从离线消息表中删除msg_id);群下线消息的拉取流程类似:分页拉取下线消息;确认离线消息;更新last_ack_msg_id;总结群消息还是很有意思的,做一个简单的总结:群离线消息一般采用pull方式,只存一份。不需要为每个用户存储offlinegroupmsg_id,只存储一个最近的ackgroupmessageid/time;为了保证消息的可达性,在线消息和离线消息都需要ACK;离线消息过多,可以通过分组、逐页拉取的方式进行优化;画外音:点播也可以拉,登录不拉,点击进群拉。如果收到重复消息,需要对msg_id进行去重,让用户感觉不到;【本文为专栏作者《58神剑》原创稿件,转载请联系原作者】点此查看该作者更多好文