消息推和拉的区别

对于一个可靠的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
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

本公众号内回复 “领取资源”, 下载各种编程资料;


觉得本文对你有帮助?请分享给更多人

关注「JAVA乐园」,提升IT技能          

推荐阅读:

更多精彩文章,请点击下方:阅读原文

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RabbitMQ是一种消息队列中间件,支持模式和模式两种消息传递方式。 模式是指消息发送者将消息直接送到队列中,之后消费者可以从队列中获取这些消息模式的优点是实时性好,消息可以立即被消费者获取,适用于对实时性要求较高的场景。模式的缺点是消息发送者无法控制消息被哪些消费者获取,可能会导致浪费消息模式是指消费者主动从队列中消息,消费者需要在队列中轮询,判断是否有消息需要被消费。模式的优点是可以控制消息被哪些消费者获取,避免消息浪费,适用于对消息精细控制的场景。模式的缺点是实时性差,需要消费者轮询队列,可能会出现消息延迟。 下面是RabbitMQ使用模式和模式的示例代码: 模式示例代码: ``` import pika # 建立连接 connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # 声明队列 channel.queue_declare(queue='hello') # 发送消息 channel.basic_publish(exchange='', routing_key='hello', body='Hello World!') print(" [x] Sent 'Hello World!'") # 关闭连接 connection.close() ``` 模式示例代码: ``` import pika # 建立连接 connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # 声明队列 channel.queue_declare(queue='hello') # 定义回调函数 def callback(ch, method, properties, body): print(" [x] Received %r" % body) # 从队列中获取消息 channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True) print(' [*] Waiting for messages. To exit press CTRL+C') channel.start_consuming() ``` 在模式中,消费者需要定义一个回调函数callback,用于接收从队列中获取到的消息。之后调用basic_consume方法,指定需要从哪个队列中获取消息,并指定回调函数。最后调用start_consuming方法,开始从队列中获取消息,并调用回调函数进行处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值