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

群消息已读回执(这屌),是推还是拉?

时间:2023-03-18 14:12:13 科技观察

每次发微信,都希望对方能尽快看到,尽快回复,但还是不知道对方有没有看。每当我收到无法立即回复的微信消息时,我都会默默地回复,假装没有看到。画外音:不想回复的人,唉,你不过是个好人罢了。微信用于个人社交,产品设计,在线状态,强制阅读回执可能会暴露个人隐私,所以微信没有相关功能。钉钉用于商务沟通,其“强制阅读回执”功能让职场人无法“假装不在线”或“假装没收到”。更重要的是,钉钉的群还有“强制阅读回执”功能。当你在群里发消息时,你可以知道谁读了谁没有读。群消息的流程是怎样的,接收者如何保证群消息被接收,发送者如何接收已读回执,是拉取还是推送是今天要讨论的话题。1、群消息的传递流程及可达性保障。大家跟着主持人的节奏,一步步看如何设计群消息。核心问题1:群消息只保存一份?或者,为每个成员保存一份?答:保存一份,为每个成员设置一个群消息队列。会有很多数据冗余,不适合。核心问题2:如果群消息只有一份,如何知道每个成员都阅读了哪些消息?答:可以利用群消息的偏序关系来记录每个成员的last_ack_msgid(last_ack_time)。已读,此消息后的消息未读。该解决方案意味着对于组中的每个用户,只需要记录一个值。回答完以上两个核心问题,就很容易得到群消息的核心数据结构了。群消息表:记录群消息。group_msgs(msgid,gid,sender_uid,时间,内容);各字段的含义为:消息ID、群组ID、发送者UID、发送时间、发送内容。群成员表:记录群中的成员,以及每个成员最近收到的群消息。group_users(gid,uid,last_ack_msgid);各字段的含义为:群ID、群成员UID、群成员最后收到的群消息ID。核心数据结构设计好后,我们来看一下群发消息的流程。业务场景:一个组中有四个成员A,uid1,uid2,uid3;A、uid1、uid2在线,期望实时收到在线消息;uid3下线,期待后续拉取下线消息;整个消息发送流程1-4如上图:A发送一条群消息;服务端收到消息后,首先要向地面发送群消息,其次要查看群中有哪些群成员,以实现推送。群组成员,实现推送;在这个过程中,只要完成消息的第二步,就可以保证群消息不会丢失。核心问题3:如何保证接收方一定会收到群消息?答:每个成员收到消息后,修改每个群成员的last_ack_msgid,告诉系统消息已经收到。在线消息和离线消息的last_ack_msgid的修改是不同的。对于在线群友,收到群消息后,会立即ack并修改last_ack_msgid。对于离线群好友,所有未读的群离线消息会在下次登录时抓取,并将last_ack_msgid改为最新消息。核心问题4:如果ack丢了,群成员会不会拉取重复的群消息?答:可以,可以根据msgid在客户端本地去重。即使系统收到重复消息,也能保证良好的用户体验。以上过程只能保证接收方收到消息。发件人仍然不知道谁在线阅读了消息,谁没有离线阅读消息。已读回执尚未实现。系统设计上会有什么样的阅读回执?影响如何?二、阅读回执流程。对于发件人发送的任何群消息,都需要知道有多少人阅读了该消息,有多少人没有阅读。需要一个基本表来记录这种关系。消息回执表:用于记录消息的已读回执。msg_acks(sender_uid,msgid,recv_uid,gid,if_ack);各字段含义为:发送方UID、消息ID、回执方UID、组ID、回执标记。添加已读回执逻辑后,群消息的流向会略有变化。第二步,服务器收到消息后,除:登陆群消息;查询群内有哪些群成员进行推送;此外,还需要:插入每条消息的初始接收状态;receiver修改last_ack_msgid的过程,会变成:sendackrequest;修改last_ack_msgid,修改已读回执if_ack状态;查询发件人在线状态;将已读回执实时推送给发送者(如果发送者在线);如果发送者不在线,ta会在下次登录时:从关联表中拉取每条消息的已读回执;这里的初步结论是:如果发件人在线,则实时推送已读回执;如果发件人不在线,则下次在线时拉取已读回执;3、流程优化方案再次详细分析下,群消息已读回执的“消息风暴扩散系数”,假设每个群有200个用户,20%的用户在线,即40个用户在线。群用户每发送一条群消息,会有:40条消息通知群友;40acks修改last_ack_msgid并发送给服务器;40已读回执通知发件人;可见消息风暴的扩散系数非常大。同时:需要存储40条ack记录;随着群、群好友、群消息的增多,存储也会成为一个问题。(1)有优化方案吗?群消息的推送可以改成接收者轮询拉取吗?答:不是,消息接收和实时性是核心指标。(2)last_ack_msgid的修改,真的需要对每条群消息都ack吗?答:其实没必要,可以分批ack,一共收到N组消息(比如10条),然后把last_ack_msgid发给服务端修改请求,同时修改已读回执本次请求之前的所有请求,这样发送给服务器的40个ack请求量可以减少到原来的1/10。(3)会带来什么副作用?答:last_ack_msgid的作用是记录接收方最近取到的一条群消息。如果不是实时更新,可能会导致部分群消息在异常退出时来不及更新last_ack_msgid,使得下次首次登录时,重复的群消息被拉取。不过这不是问题,客户端可以根据msgid进行去重,不会影响用户体验。(4)发送方在线时,发送已读回执真的需要实时推送吗?答:其实不是的,发件人每发送一条消息,都会收到40条已读回执,是轮询拉取的(比如每分钟一次,每小时只有60条请求),可以大大减少发送量要求。画外音:或者直接放入应用层keepalive请求,实现零额外请求增加。(5)会带来什么副作用?答:已读回执不是实时更新的。在最坏的情况下,收据会在1分钟内更新。当然,这个轮询时间可以配置为基于性能和产品体验的折衷。(6)如何减少数据量?答:收据数据不是核心数据读取消息,可以物理删除,而不是标记为删除;长期超过N的收据应存档或删除;4.群消息已读回执汇总,一般来说:如果发件人在线,实时推送已读回执;如果发件人不在线,则在发件人下次在线时拉取已读回执;如果要优化的话,可以:接收方一共收到了N组消息被批量确认;发件人轮询以拉取已读回执;物理删除已读回执数据,定期删除或归档非核心历史数据;推还是拉?任何倒闭的架构设计都是流氓。【本文为专栏作者《58神剑》原创稿件,转载请联系原作者】点此阅读更多该作者好文

猜你喜欢