消息推和拉的区别

对于一个可靠的IM系统,需要保证消息的百分之百到达对端。即使是在极端情况下丢失一条消息也是不能容忍的。一个极其极其低概率的事件,若是放大到分布式系统中,那这个概率事件就成了必然事件。在开发测试中如果发现一次偶然的消息丢失问题而忽略不查,那上线之后就必然会发生消息丢失。所以作为技术,一定不能放过任何一个极端情况下发生的问题。

在服务器给客户端发送消息的过程中,有两种方式,1.主动推送消息给客户端;2.客户端来拉消息。这两种方式都可以达到目的,下面就来分析一下两者的区别。

A   ->  SERVER  -> B

上述描述一种简单模型,A给B发送一条消息,首先会到达服务器,然后服务器将消息转发给客户端B。这是推的方式,服务器是直接将消息推给客户端的。那在复杂的网络环境中,如何保证消息能够到达B端?

这个例子有两处需要做保证,第一是如何保证A发出去的消息成功到达服务器。第二处是服务器推给B的消息如何知道已经成功送达。

本文主要分析第二条。

在正常情况下,服务器直接把消息下发给B端就完事了,这也是大家最希望看到的结果。如果仅仅这样处理,那系统会常常因为这个环节丢消息,而且非常严重。我们需要考虑以下几种情况。第一,对方不在线怎么办。第二,在移动网络下,信号经常会不稳定,比如乘坐地铁过隧道,信号会中断,会导致消息没有成功到达对端。如何保证消息可靠抵达?

1.当知道对端不在线的情况下,将消息存在服务器,等待客户端下次登陆来拉取。

2.对于没推成功的情况,服务器增加重推的机制,客户端收到消息后给服务器回复确认,服务器取消后续推送。

新增的逻辑引入新的复杂度,需要解决。

1.要确保成功将消息存储在服务器,如果存储失败,算是丢失消息。这样就要对存储失败的情况做检测。一种是明确知道存失败了,另一种是后端服务超时,不知道有没有存成功。存储失败可以重试,存储超时也可以简单认为是存储失败,再重试。只要保证多次存储同一消息是幂等操作就可以,防止存了两条。

2.对于重推,服务器要实现重推逻辑,把推送操作加到定时器里面,同时缓存这条消息。超时未收到客户端的确认就再推一次。由于网络原因或者客户端卡住,会导致推送的消息到达了客户端,但是客户端的确认一直没有到达服务器,导致服务器推送了多次消息,所以客户端需要对消息做重复消息的过滤。其次是多次推送后,客户端一直没有回复确认,这个可能是网络原因,客户端真没收到,也可能客户端收到了,客户端的确认还在路上,但是已经到了服务器重推次数,服务器决定要不要将消息存储到服务器?鉴于客户端实现了消息过滤机制,此处可以简单地存储消息到服务器。这就走1的逻辑。后续客户端再上线时拉服务端存储的消息,并做重复过滤。能保证消息不丢失。

3.既然会拉取之前存储在服务器的消息,那拉取完成之后需要将服务器存储的消息删除,这一步客户端在确认收到消息后再发删除请求即可。否则每次都会拉一遍,耗费流量,而且消息多了会导致登陆后的收消息流程越来越卡,由于有过滤机制,不会出现重复消息显示。

上述是推的方式实现消息可靠送达的复杂度。之间还有些逻辑没包含进来,比如push。客户端没收到消息应该改推push。那这样一来推push的情况就有很多。公司之前的老系统是采用推的方式,我们在这一块踩过很多坑,服务端的实现逻辑也相当复杂,各种判断,包括存消息到服务器,重推消息给客户端,推push给客户端,考虑多设备问题,根据客户端的确认做重推取消等等。个人看法是:相信我,如果这样做,后果很严重,你会因频繁的消息丢失问题沉浸在复杂的代码逻辑中无法自拔,甚至,开始怀疑人生。

接下来分析下拉消息的实现方式。

A  -> SERVER ->  B

B <--> SERVER

如果说推的方式是一步到位,那拉消息的实现方式分为两步。第一,A将消息发送到服务器,服务器存储这条消息,并发送一个通知给客户端B,告诉他有消息来了,快来拉取。第二,客户端来拉取这条消息,收到后删除服务器的这条消息。同样的问题,有两点需要注意。第一,网络原因导致通知没到对端,第二,对方不在线怎么办?如何保证消息可靠抵达?

拉的方式下,可以先将消息存储到服务器,再来给发送者和接收者推通知。如果消息存储失败,就可以简单回复消息发送失败给发送者,让发送者手动重发。对于后续流程,如下:

1.如果对方不在线,就不推送通知,直接结束消息发送流程,等待后续对方上线拉消息。这里就不用考虑存储消息失败的情况了,因为存储步骤在之前已保证ok。

2.对方在线,如果通知推送失败怎么处理?对于没推成功的情况,不再重推,等待下次上线拉消息!没错,就是这么暴力和任性。

如此可能出现的问题是消息乱序。客户端可以根据消息在服务端的生成时间排序,可以解决这个问题。就是会出现消息突然跳跃顺序。

在实现中,客户端拉消息应该是按照msgid范围拉一批消息,而且在服务端的实现中,msgid要保证递增,无重复。客户端重新联网后应先保证处理拉服务器消息的流程先走完,再处理新的消息通知。防止新的消息打乱上次的拉取逻辑,中间出现丢消息的情况。

另外一个问题是服务器连续推过来n条消息通知,客户端是不是应答这n条消息通知,去拉n次?因为客户端一次会拉一批消息,或许处理第一条的消息通知就已经把后续的新消息都拉下来了,后面的拉取就成了重复动作,会导致消息拉重复了。这种问题也好解决,从服务器拉取回来后,判断最大msgid是否比收到的通知中msgid要大,如果是,就忽略小的通知,不拉。如果拉到的最大msgid要比通知里的msgid小,就应该继续拉取。当然,客户端对消息的重复过滤逻辑还是要有的。

这样的做法是在网络交互上,多了一步通知和拉取,耗费一些客户端的流量。服务端的实现逻辑复杂度大大降低,客户端需要多处理一些逻辑。

这两种方案,我个人倾向于拉的方式。然后其中有几个技术实现细节后续再写。比如如何保证在分布式环境下msgid连续递增不重复,如何保证客户端对消息的排序,消息同步的具体方案,服务端对消息的存储等。

总体而言,推的方案,服务端需要处理复杂的逻辑,客户端需要处理的相对较少。拉的方案,服务端需要处理的逻辑比较简单,客户端需要配合做一些保证。对于消息可靠的保证方面,个人倾向于拉的方案,更靠谱。

------------------------------------------------

最近在维护个人博客,欢迎交流。

博客地址:知识共享



作者:黑哥儿666
链接:https://www.jianshu.com/p/cc9fdddb14c9
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @ 2019-12-29 11:05  gao88  阅读(743)  评论(0编辑  收藏  举报