熟悉MQTT协议的同学们一定知道,MQTT的publish有三个QoS,0,1,2。他们分别是:
- QoS0,最多一次送达。也就是发出去就fire掉,没有后面的事情了。
- QoS1,至少一次送达。发出去之后必须等待ack,没有ack,就要找时机重发
- QoS2,准确一次送达。消息id将拥有一个简单的生命周期。
QoS0
QoS 0 对服务器来说很好处理,什么都不存,找到要发给谁,我给你发,收不收得到,不管。他的协议大概是这样的:
QoS1
QoS 1 也好理解,我发一个带messageid的消息出去,对方收到了,给我回一个带messageid的ack,我才认为数据收到了。他的协议交互大概是这样的:
这个过程中,有可能出现问题的是,publish的下发,以及ack的回复。
- 如果publish下发出现问题,将没有puback回复,服务器将找机会重新下发该msgid的消息。
- 如果ack回复出现问题,服务器认为没有收到确认,仍然重新找机会重新下发该msgid的消息。
- 客户端会收重复收到该msgid的消息,需自行去重。与QoS1的至少一次送达没有矛盾
QoS2
QoS2就比较复杂了。协议的下发要求是这样的:
这里协议的思路是,多一个确认,“保证”这个消息是只下发一次的。另外,服务器收到pubrec之后,应该讲消息删除,转而保存pubrec,等待pubcomp,并且下次重发pubrel即可。然而,实际,事情要复杂的多。我们来看下面的情景:
- publish下发失败了,服务器重发publish。
- pubrec上报失败了,服务器重发publish。这个时候,客户端仍然是重复收到多次publish。
- pubrel下发失败了,服务器重发pubrel。
- pubcomp上报失败了,服务器重发pubrel。
事实上,publish仍旧可能重复发送多次。但是这个协议能够保证协议上的应用层收到准确一次的消息,这个保证需要服务器和客户端同时正确处理消息:
- 接收者收到publis的QoS2的消息之后,客户端需要保存一个msgid的记录,并且进入一个状态,即之后不管来了几个这个msgid的消息,都不管他,认为是重复的,丢弃。
- 接收到publish的QoS2消息之后,不能马上投递给上层,而是在本地做持久化,将消息保存起来。一个点是这里这里需要是持久化,而不是保存在内存。单纯保存在内存,是不能真正做到QoS2的。
- 收到publish的QoS2消息之后,马上回复一个pubrec给发送端。
- 服务器在收到pubrec之后,应该认为客户端已经收到消息,将publish的消息转入等待pubcomp的阶段,不再重发publish,转而下发pubrel
- 客户端收到pubrel之后,正式将消息投递给上层应用层。
- 投递之后,销毁该msgid,返回pubcomp给服务器,销毁之前的持久化消息。
- 之后不管服务器来多少pubrel,都没有messagid的记录,只需要回复pubcomp,不需要投递给上层。
所以完整的流程应该是这样的:
然而,即使是这样,仍然是存在重发投递的,只是风险小了很多而已。这里出现重复投递的地方,就是在pubrel之后,投递给上层的时候出现的。但是因为是本地投递,出现问题的概率,相对低很多。
同时,为了保证消息在rel之后才投递给上层,不得不使用本地化存储,这对嵌入式设备来说,实际上比较伤。如果不使用本地化存储,程序在中间出问题,很难处理。如果有好办法,请务必分享给我。。。
总的来说,QoS2实际上是一个很美好的愿景,然而在我看来,并不存在真正的QoS2,因为总是有出问题的可能性,只是概率多大而已。典型的二将军问题。