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

为什么微信不丢“离线消息”?

时间:2023-03-14 01:16:42 科技观察

需求来源当发送方用户A向接收方用户B发送消息时,如果用户B在线,如前文《微信为啥不丢“在线消息”?》所讨论的,可以通过应用层确认,发送方超时重传,以及接收方的重复数据删除确保业务级消息不丢失或重复。如果接收用户B不在线,系统如何保证消息的可达性?这是本文要探讨的问题。问:接收方离线时发送消息的流程是怎样的?答:如上图所示,(1)用户A给用户B发送消息(2)服务器查看用户B状态为离线(3)服务器将消息存入DB(4)服务器返回给用户A发送成功(对于发送方来说,消息落地DB就认为发送成功)问题:离线消息表的设计,拉下线的过程?receiver_uid,msg_id,time,sender_uid,msg_type,msg_content...访问方式:接收方B想拉取发送方A发送的离线消息给TA,只需要在receiver_uid(B),sender_uid(A)上查询,删除离线消息,并将消息返回给B就可以了。整体流程如上图所示,(1)用户B拉取用户A发送给ta的离线消息(2)服务器从DB中拉取离线消息(3)服务器从DB中删除离线消息(4)服务器返回给用户B想要的离线消息:上述过程有问题?答:如果用户B好友较多,客户端登录时需要拉取所有好友的离线消息,客户端与服务端交互较多。客户端伪代码:for(alluidinB'sfriend-list){//所有好友必须拉取get_offline_msg(B,uid);//与服务端交互}优化方案1:先拉取每个好友的离线消息数,真实用户B在进去查看离线消息时,向服务端发送拉取请求(手机端为了节省流量,经常使用这种按需拉取优化)。优化方案2:一次性拉取好友发给用户B的所有离线消息,然后发送给客户端根据sender_uid在本地计算。这样学校留言表的访问方式就变成了->只需要根据receiver_uid查询即可。登录时与服务器的交互次数减少为1次。问题:用户B一次性拉取好友发给他的所有离线消息。消息量大时,请求包大、慢、容易卡顿。答:分页拉取,根据业务需要,先拉取最新(或最旧)的新闻页面,再根据需要逐页拉取。问题:如何保证可达性。上述步骤的第三步完成后,第四步将离线消息返回给客户端。如果服务器挂了,路由器丢了消息,或者客户端死机了,那么下线消息就丢了。是吗(数据库已经删除,用户还没有收到)?答:嗯,如果按照上面的1、2、3、4步骤,是的,如何保证离线消息的可达性呢?像在线消息的应用应用层的ACK机制一样,拉取离线消息时,不能直接删除数据库中的离线消息,必须等待应用层的离线消息ACK(说明用户B在删除数据库中的离线消息之前确实收到了离线消息。问题:如果用户B拉取了一页离线消息,但是在ACK之前就crash了,下次登录时是否会拉取重复的离线消息?答:如果下线消息被拉取但是没有ACK离线消息,服务器不会删除之前的页面,所以系统层面也会在下次登录时拉取。但是在业务层面,可以根据msg_id。SMC理论:保证消息不丢失、不重复在系统层面是不可能的,但是在业务层面是可以做到的,而且用户是无意识的。问题:假设有N页离线消息,现在每条离线消息都需要一个ACK,那么客户端和服务端的交互次数又翻倍了?有优化空间吗?答:不需要每页消息都ACK,拉第二页的消息就相当于第一页消息的ACK。这时候服务器可以在第一页删除离线消息,然后在最后一页ACK消息。这样做的效果是,无论拉取多少页的离线消息,都只会多一次ACK请求,多一次与服务器的交互。综上所述,“线下消息”的可访问性可能比大家想象的要复杂。常见的优化有:(1)对于同一个用户B,一次性拉取所有用户发送给TA的离线消息,然后在客户端本地根据发送方的分析,与一条条拉取消息相比发件人表示,可以大大减少服务端的交互次数(2)Pagingpull,无线端常见的优化(3)ApplicationlayerACK,应用层的去重,可以保证离线消息不lostorrepeated(4)拉下一个页面,作为上一个页面的ACK,可以大大减少与服务器的交互次数。文章转载自微信公众号《建筑师之路》