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

群聊比单聊复杂,为什么这么复杂?_0

时间:2023-03-20 23:47:43 科技观察

群聊是多人社交的基本需求。群成员在群内发送消息,希望实现:在线的群成员能在最晚的时间收到消息;离线群成员登录消息后可接收消息;群组消息离线消息的实时性、可达性、复杂度远高于一对一消息。常见的群发流程是怎样的?集团业务有两个核心数据结构。群成员表:t_group_users(group_id,user_id)画外音:用来描述一个群有多少成员。群组离线消息表:t_offine_msgs(user_id,group_id,sender_id,time,msg_id,msg_detail)画外音:用于描述群组成员的离线消息。业务场景示例:假设一个群里有5个成员x,A,B,C,D,成员x发送一条消息;成员A和B在线,希望实时收到消息;成员C和D下线,期待未来拉下线消息;典型的群消息发送流程,如图中步骤1-4所示:步骤1:群消息发送者x向服务器发送群消息;第二步:服务器去db查询组里有多少用户(x,A,B,C,D);第三步:服务器在缓存中检查这些用户的在线状态;第四步:针对群内在线用户A、B,群消息服务器发送实时推送;第五步:对于群内在线用户对于离线用户C和D,群消息服务器进行离线存储;典型的群下线消息拉取流程如图中步骤1-3所示:第1步:下线消息拉取器C从服务端拉取群下线消息;steps2:服务端从db中拉取离线消息,返回groupuserC;第三步:服务器从数据库中删除群用户C的群下线消息;那么,问题来了!对于同一个群消息的内容,多个离线用户似乎要保存很多份。假设群内有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;优化后的流程,消息的可达性能保证吗?例如:在线消息传递可能导致消息丢失,如服务器重启、路由器丢失、客户端崩溃等;离线消息拉取也可能导致消息丢失,原因同上;画外音:和单对单消息的可靠传递一样,是通过添加应用层的ACK来实现的。群发消息呢?如何通过群消息的应用层ACK保证消息的可靠传递?应用层ACK优化后,再次发生群在线消息发送做了一些改动:第3步:群消息表存储消息msg_detail后,无论用户在线与否,先将msg_id存入离线消息表;第六步:在线用户A和B收到群消息后,需要添加一个应用层ACK来标识消息的到达;第七步:在线用户A和B在应用层ACK后删除自己的离线消息msg_id;从群中拉取离线消息也是如此:第一步:先拉取getmsg_id;第三步:再次拉取msg_detail;第五步:***应用层ACK;第六步:服务端收到应用层ACK后,只能删除离线消息表中的msg_id;如果消息被拉取,应用层没有时间ACK,会不会收到重复的消息?似乎是,但是您可以在客户端对它们进行重复数据删除。对于重复的msg_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)画外音:仍然用于存放一个群内所有的消息内容。群组离线消息表:不再需要。离线消息表优化后,群在线消息的下发流程:Step3:消息msg_detail存入群消息表后,不再需要操作离线消息表(需要将msg_id插入到群消息表中)优化前离线消息表);第七步:在线用户A和B可以在应用层更新ACK后的last_ack_msg_id(优化前需要将离线消息表中的msg_id删除);群下线消息的拉取流程也类似:第一步:拉取下线消息;第三步:ACK离线消息;第四步:更新last_ack_msg_id;增加ACK机制,保证群消息的可靠传递。假设一个群有500个用户,“每条”群消息都会变成500个应用层ACK,这似乎对服务器造成了巨大的危害影响。有什么办法可以减少ACK请求的数量吗?批量ACK是减少请求量的常用方法。如果每条群消息都被ACK,确实会对服务器造成巨大的影响。为了减少ACK请求量,可以批量处理ACK,批量处理ACK有两种方式:每收到N组消息,就ACK一次,这样请求量就减少到原来的1/N;每间隔T一个群消息ACK可以达到类似的效果;batchACK可能会带来新的问题:如果用户还没有来得及ACK群消息,用户就会退出,这样下次登录好像会拉取重复的离线消息。我应该怎么办?客户端根据msg_id去重,不显示给用户,保证良好的用户体验。群里离线消息太多,拉的太慢,怎么办?按页面拉取(pullondemand),细节不再展开,都是常见的优化方案。总结群消息还是很有意思的,我简单总结一下:无论是群在线消息还是群离线消息,应用层的ACK都是可达性的保证;只保存一份群消息,无需为每个用户msg_id保存离线群消息,只需要保存一个群消息id/最近一次ack的时间;为了减少消息风暴,可以批量ACK;如果收到重复消息,需要去重msg_id,让用户感觉不到;离线消息过多,可以通过分页拉取(On-demandpull)优化;【本文为专栏作者《58神剑》原创稿件,转载请联系原作者】点此查看该作者更多好文