MQTT QoS 0、1、2 解析:快速入门指南
MQTT QoS 0、1、2 解析:快速入门指南
在不稳定的网络环境中,使用TCP传输协议的MQTT可能会面临确保可靠通信的挑战。为了解决这个问题,MQTT引入了一种QoS机制,提供多种消息交互选项,以满足用户在不同场景下对可靠消息传递的特定要求。
MQTT QoS简介
MQTT中的QoS指的是发布者与订阅者之间消息传递的保证级别。它提供三个服务级别:
- QoS 0 – 最多交付一次
- QoS 1 – 至少交付一次
- QoS 2 – 只交付一次
其中,使用QoS 0可能会丢失消息,使用QoS 1可以保证收到消息,但消息可能重复,使用QoS 2可以保证消息既不丢失也不重复。QoS等级从低到高,不仅意味着消息可靠性的提升,也意味着传输复杂程度的提升。
在一个完整的从发布者到订阅者的消息投递流程中,QoS等级是由发布者在PUBLISH报文中指定的,大部分情况下Broker向订阅者转发消息时都会维持原始的QoS不变。不过也有一些例外的情况,根据订阅者的订阅要求,消息的QoS等级可能会在转发的时候发生降级。
例如,订阅者在订阅时要求Broker可以向其转发的消息的最大QoS等级为QoS 1,那么后续所有QoS 2消息都会降级至QoS 1转发给此订阅者,而所有QoS 0和QoS 1消息则会保持原始的QoS等级转发。
接下来,让我们来看看MQTT中每个QoS等级的具体原理。
MQTT QoS等级详解
QoS 0 - 最多交付一次
QoS 0是最低的QoS等级。QoS 0消息即发即弃,不需要等待确认,不需要存储和重传,因此对于接收方来说,永远都不需要担心收到重复的消息。
为什么QoS 0消息会丢失?
当我们使用QoS 0传递消息时,消息的可靠性完全依赖于底层的TCP协议。而TCP只能保证在连接稳定不关闭的情况下消息的可靠到达,一旦出现连接关闭、重置,仍有可能丢失当前处于网络链路或操作系统底层缓冲区中的消息。这也是QoS 0消息最主要的丢失场景。
QoS 1 - 至少交付一次
为了保证消息到达,QoS 1加入了应答与重传机制,发送方只有在收到接收方的PUBACK报文以后,才能认为消息投递成功,在此之前,发送方需要存储该PUBLISH报文以便下次重传。
QoS 1需要在PUBLISH报文中设置Packet ID,而作为响应的PUBACK报文,则会使用与PUBLISH报文相同的Packet ID,以便发送方收到后删除正确的PUBLISH报文缓存。
为什么QoS 1消息会重复?
对于发送方来说,没收到PUBACK报文分为以下两种情况:
- PUBLISH未到达接收方
- PUBLISH已经到达接收方,接收方的PUBACK报文还未到达发送方
在第一种情况下,发送方虽然重传了PUBLISH报文,但是对于接收方来说,实际上仍然仅收到了一次消息。
但是在第二种情况下,在发送方重传时,接收方已经收到过了这个PUBLISH报文,这就导致接收方将收到重复的消息。
虽然重传时PUBLISH报文中的DUP标志会被设置为1,用以表示这是一个重传的报文。但是接收方并不能因此假定自己曾经接收过这个消息,仍然需要将其视作一个全新的消息。
这是因为对于接收方来说,可能存在以下两种情况:
第一种情况,发送方由于没有收到PUBACK报文而重传了PUBLISH报文。此时,接收方收到的前后两个PUBLISH报文使用了相同的Packet ID,并且第二个PUBLISH报文的DUP标志为1,此时它确实是一个重复的消息。
第二种情况,第一个PUBLISH报文已经完成了投递,1024这个Packet ID重新变为可用状态。发送方使用这个Packet ID发送了一个全新的PUBLISH报文,但这一次报文未能到达对端,所以发送方后续重传了这个PUBLISH报文。这就使得虽然接收方收到的第二个PUBLISH报文同样是相同的Packet ID,并且DUP为1,但确实是一个全新的消息。
由于我们无法区分这两种情况,所以只能让接收方将这些PUBLISH报文都当作全新的消息来处理。因此当我们使用QoS 1时,消息的重复在协议层面上是无法避免的。
甚至在比较极端的情况下,例如Broker从发布方收到了重复的PUBLISH报文,而在将这些报文转发给订阅方的过程中,再次发生重传,这将导致订阅方最终收到更多的重复消息。
在下图表示的例子中,虽然发布者的本意只是发布一条消息,但对接收方来说,最终却收到了三条相同的消息:
以上,就是QoS 1保证消息到达带来的副作用。
QoS 2 - 只交付一次
QoS 2解决了QoS 0、1消息可能丢失或者重复的问题,但相应地,它也带来了最复杂的交互流程和最高的开销。每一次的QoS 2消息投递,都要求发送方与接收方进行至少两次请求/响应流程。
- 首先,发送方存储并发送QoS为2的PUBLISH报文以启动一次QoS 2消息的传输,然后等待接收方回复PUBREC报文。这一部分与QoS 1基本一致,只是响应报文从PUBACK变成了PUBREC。
- 当发送方收到PUBREC报文,即可确认对端已经收到了PUBLISH报文,发送方将不再需要重传这个报文,并且也不能再重传这个报文。所以此时发送方可以删除本地存储的PUBLISH报文,然后发送一个PUBREL报文,通知对端自己准备将本次使用的Packet ID标记为可用了。与PUBLISH报文一样,我们需要确保PUBREL报文到达对端,所以也需要一个响应报文,并且这个PUBREL报文需要被存储下来以便后续重传。
- 当接收方收到PUBREL报文,也可以确认在这一次的传输流程中不会再有重传的PUBLISH报文到达,因此回复PUBCOMP报文表示自己也准备好将当前的Packet ID用于新的消息了。
- 当发送方收到PUBCOMP报文,这一次的QoS 2消息传输就算正式完成了。在这之后,发送方可以再次使用当前的Packet ID发送新的消息,而接收方再次收到使用这个Packet ID的PUBLISH报文时,也会将它视为一个全新的消息。
为什么QoS 2消息不会重复?
QoS 2消息保证不会丢失的逻辑与QoS 1相同,所以这里我们就不再重复了。
与QoS 1相比,QoS 2新增了PUBREL报文和PUBCOMP报文的流程,也正是这个新增的流程带来了消息不会重复的保证。
在我们更进一步之前,我们先快速回顾一下QoS 1消息无法避免重复的原因。
当我们使用QoS 1消息时,对接收方来说,回复完PUBACK这个响应报文以后Packet ID就重新可用了,也不管响应是否确实已经到达了发送方。所以就无法得知之后到达的,携带了相同Packet ID的PUBLISH报文,到底是发送方因为没有收到响应而重传的,还是发送方因为收到了响应所以重新使用了这个Packet ID发送了一个全新的消息。
所以,消息去重的关键就在于,通信双方如何正确地同步释放Packet ID,换句话说,不管发送方是重传消息还是发布新消息,一定是和对端达成共识了的。
而QoS 2中增加的PUBREL流程,正是提供了帮助通信双方协商Packet ID何时可以重用的能力。
QoS 2规定,发送方只有在收到PUBREC报文之前可以重传PUBLISH报文。一旦收到PUBREC报文并发出PUBREL报文,发送方就进入了Packet ID释放流程,不可以再使用当前Packet ID重传PUBLISH报文。同时,在收到对端回复的PUBCOMP报文确认双方都完成Packet ID释放之前,也不可以使用当前Packet ID发送新的消息。
因此,对于接收方来说,能够以PUBREL报文为界限,凡是在PUBREL报文之前到达的PUBLISH报文,都必然是重复的消息;而凡是在PUBREL报文之后到达的PUBLISH报文,都必然是全新的消息。
一旦有了这个前提,我们就能够在协议层面完成QoS 2消息的去重。
不同MQTT QoS的适用场景和注意事项
QoS 0
QoS 0的缺点是可能会丢失消息,消息丢失的频率依赖于你所处的网络环境,并且可能使你错过断开连接期间的消息,不过优点是投递的效率较高。
所以我们通常选择使用QoS 0传输一些高频且不那么重要的数据,比如传感器数据,周期性更新,即使遗漏几个周期的数据也可以接受。
QoS 1
QoS 1可以保证消息到达,所以适合传输一些较为重要的数据,比如下达关键指令、更新重要的有实时性要求的状态等。
但因为QoS 1还可能会导致消息重复,所以当我们选择使用QoS 1时,还需要能够处理消息的重复,或者能够允许消息的重复。
在我们决定使用QoS 1并且不对其进行去重处理之前,我们需要先了解,允许消息的重复,可能意味着什么。
如果我们不对QoS 1进行去重处理,我们可能会遭遇这种情况,发布方以1、2的顺序发布消息,但最终订阅方接收到的消息顺序可能是1、2、1、2。如果1表示开灯指令,2表示关灯指令,我想大部分用户都不会接受自己仅仅进行了开灯然后关灯的操作,结果灯在开和关的状态来回变化。
QoS 2
QoS 2既可以保证消息到达,也可以保证消息不会重复,但传输成本最高。如果我们不愿意自行实现去重方案,并且能够接受QoS 2带来的额外开销,那么QoS 2将是一个合适的选择。通常我们会在金融、航空等行业场景下会更多地见到QoS 2的使用。
关于MQTT QoS的Q&A
如何为QoS 1消息去重?
在我们介绍QoS 1的时候讲到,QoS 1消息的重复在协议层面上是无法避免的。所以如果我们想要对QoS 1消息进行去重,只能从业务层面入手。
一个比较常用且简单的方法是,在每个PUBLISH报文的Payload中都带上一个时间戳或者一个单调递增的计数,这样上层业务就可以根据当前收到消息中的时间戳或计数是否大于自己上一次接收的消息中的时间戳或计数来判断这是否是一个新消息。
何时向后分发QoS 2消息?
我们已经了解到,QoS 2的流程是非常长的,为了不影响消息的实时性,我们可以在第一次收到PUBLISH报文时,就启动消息的向后分发。当然一旦开始向后分发,后续收到在PUBREL报文之前到达的PUBLISH报文,都不能再重复分发操作,以免消息重复。
不同QoS的性能有差距么?
以EMQX为例,在相同的硬件配置下进行点对点通信,通常QoS 0与QoS 1能够达到的吞吐比较接近,不过QoS 1的CPU占用会略高于QoS 0,负载较高时,QoS 1的消息延迟也会进一步增加。而QoS 2能够达到的吞吐一般仅为QoS 0、1的一半左右。
结语
通过理解和选择适合您MQTT设置的QoS级别,您可以优化物联网网络的性能和可靠性。无论您是使用QoS 0处理简单传感器数据,还是使用QoS 2管理关键操作,或者在QoS 1中寻求平衡,MQTT灵活的QoS系统都能满足您的需求。