《架构师之路:架构设计中的100个知识点》
54.离线消息可靠投递
《微信:我们绝不丢消息!》提到,单人实时聊天消息的可靠投递,是通过应用层的超时、重传、确认、去重来保证的。
那如果没有打开手机,没有登录微信,好友发给我的微信消息,有没有可能丢失呢?今天和大家聊聊离线消息的话题。
没有做过IM业务的架构师可能会说,离线消息存储数据库不就行了吗?可事实上,远比你想的复杂。
接收方不在线,消息发送流程是怎么样的?
如上图所述,A给B发了一条消息,而B不在线,离线消息存储的流程如下:
1. A发送消息给B,通过server中转;
2. server查看用户B的状态为offline;
3. server将消息存储到DB中;
4. server返回用户A发送成功,并带上特殊标识,避免A重发;
离线消息表如何设计?
很容易想到,消息业务有这样的一些关键属性:
t_offline_msg( receiver_uid, // 离线消息接收方 msg_id, // 消息ID time, // 消息发送时间 sender_uid, // 消息发送方 msg_type, // 消息类型 msg_content, // 消息内容 …);
B登陆之后,如何拉取A发给他的离线消息呢?
(receiver_uid(B), sender_uid(A))
在上述索引查询,然后把离线消息删除,再把消息返回B即可。
整体流程如上图所述:
1. B拉取A发送给ta的离线消息;
2. server从DB中拉取离线消息;
3. server从DB中把离线消息删除;
4. server返回给用户B想要的离线消息;
想到这一步,也不难。
那么问题来了,B登录微信的时候,不止要拉取A发给他的离线消息,还需要拉取所有其他好友发给他的离线消息,这该如何实现呢?
如果用户B有很多好友,登录后客户端需要对所有好友进行离线消息拉取。
客户端伪代码:
get B's friend-list; // 拉取B的好友列表for(all uid in B's friend-list){ // 遍历所有好友uid get_offline_msg(B,uid); // 拉取离线消息}
如果有10000个好友,难道要拉取10000次?
画外音:我的微信好友已满员,大家猜微信好友上限是多少?
有没有减少拉取次数的优化方法呢?
按需拉取:先拉取各个好友的离线消息数量,真正查看离线消息时,才往服务器发送拉取请求。
除了减少流量的“按需拉取”优化,还有减少拉取次数的优化方案么?
一次性拉取:可以一次性通过receiver_uid即接收方ID,拉取所有好友发送给B的离线消息,把登录时与服务器的交互次数降低为了1次。到客户端本地再根据sender_uid进行计算。
问题又来了,用户B一次性拉取所有好友发给ta的离线消息,消息量很大时,一个请求包很大,速度慢怎么办?
分页拉取:根据业务需求,先拉取最新的一页消息,再按需一页页拉取。
新的问题又来了,离线消息会不会丢失,用户会不会收不到呢?
例如,上述步骤第三步执行完毕之后(删除了离线消息),第四个步骤离线消息返回给客户端过程中,服务器挂掉,路由器丢消息,或者客户端crash了,那离线消息岂不是丢了么。
画外音:数据库已删除,用户却还没看到。
如何保证离线消息的可达性?
加入ACK机制:如同在线消息的应用层ACK机制一样,离线消息拉时,不能够直接删除数据库中的离线消息,而必须等应用层的离线消息ACK,等客户端真的收到离线消息,才能删除数据库中的离线消息。
新的问题又来了,如果用户B拉取了一页离线消息,却在ACK之前crash了,下次登录时会拉取到重复的离线消息么?
拉取了离线消息却没有ACK,服务器不会删除之前的离线消息,故下次登录时系统层面还会拉取到。和在线消息投递一样,接收方通过msgid去重,系统层面会收到重复消息,但在业务层面,用户却无感知。
另一个问题,假设有N页离线消息,现在每个离线消息需要一个ACK,那么岂不是客户端与服务器的交互次数又加倍了?有没有优化空间?
其实,不用每一页消息都ACK,在拉取第二页消息时相当于第一页消息的ACK,此时服务器再删除第一页的离线消息即可,最后一页消息再ACK一次。这样的效果是,不管拉取多少页离线消息,只会多一个ACK请求,与服务器多一次交互。
稍作总结
离线消息的可靠投递,关键技术有:
1. 对于同一个用户B,一次性拉取所有用户发给ta的离线消息,能大大减少服务器交互次数;
2. 按需拉取,是无线端的常见优化;
3. 分页拉取,是一个请求次数与包大小的折衷;
4. 应用层的ACK,应用层的去重,才能保证离线消息的不丢不重;
5. 下一页的拉取,同时作为上一页的ACK,能够极大减少与服务器的交互次数;
知其然,知其所以然。
思路比结论更重要。
==全文完==
《微信:我们绝不丢消息!》中有人喷我,说:“你压根没做过IM,就在这瞎忽悠”。我可能说的不完全对,但我真的做过几十年IM。不信你看:《关于即时通讯架构的一切!》
另外,这篇文章大伙帮我转一下:
一个真诚的技术人创业了,大家多多支持。