不管是消息队列的消息投递,还是单人实时聊天的消息投递,都需要通过应用层的超时,重传,确认,去重来保证消息的可靠投递。离线消息的处理,根据实际业务需求来做处理。一般来说,要保证用户的离线消息不丢失,用户上线后能够获取离线消息。
用户A给B发送一条消息,B不在线,离线消息存储的流程如下:
1.A发送消息给B,通过服务器server中转;
2.server查看B的状态为offline离线;(服务端会缓存用户的状态)
3.server将消息存储到数据库DB;
4.server返回用户A,消息发送成功;(对于发送方而言,消息存到DB,就认为发送成功)
用户B上线了,他要拉取A给他发送的离线消息,整体流程如下:
1.B向server拉取A给B发送的离线消息; (通过uid_B,uid_A在离线消息表查询)
2.server从DB中获取离线消息;
3.server从DB中删除离线消息;
4.server将B所需要的离线消息返回给B.
这是最原始的场景。
实际上用户登录后,可能要拉取所有好友的离线信息,显然他不能一个一个的去拉取,原因是要减少拉取次数,一个合理的方式是按需拉取,即先拉取各个好友的离线消息数量,真正查看离线消息时,才往服务器发送拉取请求。
进一步优化拉取次数:一次拉取B的所有好友的离线消息,通过uid_B查询离线消息表,然后在客户端本地,根据sender_uid进行计算,区分是哪个具体的好友消息。
整体流程如下:
1.B向server拉取所有给B发送的离线消息; (通过receiver_uid=uid_B在离线消息表查询)
2.server从DB中获取离线消息;
3.server从DB中删除离线消息;
4.server将B所需要的离线消息返回给B.
问题:一次请求返回所有数据,返回的报文数据可能过大,速度会很慢
方案:分页拉取,根据业务需求,先拉取最新的一页消息,在按需一页页拉取。
整体流程如下:
1.B向server拉取所有给B发送的离线消息;
2.server从DB中按页获取离线消息;
3.server从DB中按页删除离线消息;
4.server将B所需要的离线消息按页返回给B.
问题1:如果先删除离线消息,再返回数据,在返回数据的过程中服务器挂了,路由器丢失消息,或者客户端挂了,怎么弄?
显然需要用ACK机制,离线消息拉取时不能直接删除数据库中的离线消息,而必须等应用层的离线消息ACK,等客户端真的收到离线消息,才能删除数据库中的离线消息。
问题2:如果用户B拉取了一页消息,却在ACK之前挂了,下次登录时会拉到重复的离线消息吗?
在系统层面,拉取了消息但是没有ACK,服务器不会删除之前的离线消息,故下次登录还会拉取到。但是在业务层面,可以根据msg_id去重,让用户无感知。
问题3:假如有n页离线消息,如果每页消息都需要一个ACK,那么客户端与服务器的交互次数有加倍了,如何优化?
其实,不用每一页都单独ACK一次,在拉取第二页消息时相当于第一页消息的ACK,此时服务器再删除第一页消息即可,最后一页消息在ACK一次,整个过程仅仅是在最后一页拉取成功之后加了个ACK,中间是用后面的拉取请求同时做为上一次的ACK。这样的效果是,不管拉取多少页数据,只会多一个ACK请求,与服务器多一次交互。
总结:
“离线消息”的玩法,场景的优化,有:
- 对于同一个用户B,一次性拉取所有的用户给他发送的离线信息,然后在客户端本地进行发送方分析,相比按照发送方一个个进行消息拉取,能大大减少与服务器的交互次数;
- 按需拉取;
- 分页拉取,是一个请求次数与包大小的折中方案;
- 应用层的ACK,应用层去重,才能保证消息不丢不重复;
- 下一页拉取同时作为上一页的ACK,减少与服务器的交互次数。