MQTT协议QoS2 准确一次送达的实现

熟悉MQTT协议的同学们一定知道,MQTT的publish有三个QoS,0,1,2。他们分别是:

  • QoS0,最多一次送达。也就是发出去就fire掉,没有后面的事情了。
  • QoS1,至少一次送达。发出去之后必须等待ack,没有ack,就要找时机重发
  • QoS2,准确一次送达。消息id将拥有一个简单的生命周期。

QoS0

QoS 0 对服务器来说很好处理,什么都不存,找到要发给谁,我给你发,收不收得到,不管。他的协议大概是这样的:

MQTT协议QoS2 准确一次送达的实现

 

 

QoS1

QoS 1 也好理解,我发一个带messageid的消息出去,对方收到了,给我回一个带messageid的ack,我才认为数据收到了。他的协议交互大概是这样的: MQTT协议QoS2 准确一次送达的实现

 

 

这个过程中,有可能出现问题的是,publish的下发,以及ack的回复。

  • 如果publish下发出现问题,将没有puback回复,服务器将找机会重新下发该msgid的消息。
  • 如果ack回复出现问题,服务器认为没有收到确认,仍然重新找机会重新下发该msgid的消息。
  • 客户端会收重复收到该msgid的消息,需自行去重。与QoS1的至少一次送达没有矛盾


QoS2
QoS2就比较复杂了。协议的下发要求是这样的:

MQTT协议QoS2 准确一次送达的实现

 

 

这里协议的思路是,多一个确认,“保证”这个消息是只下发一次的。另外,服务器收到pubrec之后,应该讲消息删除,转而保存pubrec,等待pubcomp,并且下次重发pubrel即可。然而,实际,事情要复杂的多。我们来看下面的情景:

  • publish下发失败了,服务器重发publish。
  • pubrec上报失败了,服务器重发publish。这个时候,客户端仍然是重复收到多次publish。
  • pubrel下发失败了,服务器重发pubrel。
  • pubcomp上报失败了,服务器重发pubrel。

事实上,publish仍旧可能重复发送多次。但是这个协议能够保证协议上的应用层收到准确一次的消息,这个保证需要服务器和客户端同时正确处理消息:

  1. 接收者收到publis的QoS2的消息之后,客户端需要保存一个msgid的记录,并且进入一个状态,即之后不管来了几个这个msgid的消息,都不管他,认为是重复的,丢弃。
  2. 接收到publish的QoS2消息之后,不能马上投递给上层,而是在本地做持久化,将消息保存起来。一个点是这里这里需要是持久化,而不是保存在内存。单纯保存在内存,是不能真正做到QoS2的。
  3. 收到publish的QoS2消息之后,马上回复一个pubrec给发送端。
  4. 服务器在收到pubrec之后,应该认为客户端已经收到消息,将publish的消息转入等待pubcomp的阶段,不再重发publish,转而下发pubrel
  5. 客户端收到pubrel之后,正式将消息投递给上层应用层。
  6. 投递之后,销毁该msgid,返回pubcomp给服务器,销毁之前的持久化消息。
  7. 之后不管服务器来多少pubrel,都没有messagid的记录,只需要回复pubcomp,不需要投递给上层。

所以完整的流程应该是这样的:
MQTT协议QoS2 准确一次送达的实现

 

 

然而,即使是这样,仍然是存在重发投递的,只是风险小了很多而已。这里出现重复投递的地方,就是在pubrel之后,投递给上层的时候出现的。但是因为是本地投递,出现问题的概率,相对低很多。

同时,为了保证消息在rel之后才投递给上层,不得不使用本地化存储,这对嵌入式设备来说,实际上比较伤。如果不使用本地化存储,程序在中间出问题,很难处理。如果有好办法,请务必分享给我。。。

总的来说,QoS2实际上是一个很美好的愿景,然而在我看来,并不存在真正的QoS2,因为总是有出问题的可能性,只是概率多大而已。典型的二将军问题。

上一篇:nginx 80端口强制转443


下一篇:苹果手机fiddler抓包时出现了tunnel to 443 解决方案,亲测有效