MQTT 提供了 QoS 机制,其核心是设计了多种消息交互机制来提供不同的服务质量,来满足用户在各种场景下对消息可靠性的要求
MQTT QoS
在网络受限的环境下,MQTT如果只依靠底层的 TCP 传输协议,并不能完全保证消息的可靠到达。因此,MQTT 提供了 QoS 机制,其核心是设计了多种消息交互机制来提供不同的服务质量,来满足用户在各种场景下对消息可靠性的要求。
QoS
由发布者在PUBLISH
报文中指定,消息的 QoS 等级可能会在转发的时候发生降级(比如订阅者在订阅时要求 Broker 可以向其转发的消息的最大 QoS 等级为 QoS 1,那么后续所有 QoS 2 消息都会降级至 QoS 1 转发给此订阅者,而所有 QoS 0 和 QoS 1 消息则会保持原始的 QoS 等级转发)
QoS 0
QoS 0
消息可靠性依赖完全底层TCP
协议,消息即发即弃,不需要等待确认,不需要存储和重传,因此对于接收方来说,永远都不需要担心收到重复的消息。
消息丢失问题
但TCP
只能保证连接不关闭的情况下消息的可靠性,在连接关闭、重置等情况下会导致消息丢失。
QoS 1
QoS 1
加入了应答与重传机制,发送方只有在接收到PUBACK
后才能认为消息投递成功, 在此之前发送方需要存储该PUBLISH
报文以便下次重传,QoS 1
采用Packet ID
来标示报文,PUBACK
需要采用和PUBLISH
报文一致的Packet ID
重复消息问题
但是QoS 1
可能会造成重复消费消息问题,PUBLISH
已经到达接收方,但接收方的PUBACK
没有传回给发送方,发送方可能触发重传,导致重复消息。
虽然重传时 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
时,消息的重复在协议层面上是无法避免的。
可以基于QoS 1 自行去重,如在报文中增加时间戳或单调计数器,接收方根据计数关系判断是否是一个新消息
QoS 2
QoS 2 解决了消息丢失和消息重复问题,但也带来了很大的开销,因为每一次QoS 2 消息传递,至少要进行两次请求响应流程。
流程如下:
- 发送方存储并发送
QoS 2
的PUBLISH
报文,然后等待接收方回复PUBREC
报文。 - 当发送方收到
PUBREC
报文,即可确认对端已经收到了PUBLISH
报文,发送方将不再需要重传这个报文,并且也不能再重传这个报文。此时发送方可以删除本地存储的PUBLISH
报文,然后发送一个PUBREL
报文,通知对端自己准备将本次使用的Packet ID
标记为可用了。与PUBLISH
报文一样,我们需要确保 PUBREL 报文到达对端,所以也需要一个响应报文,并且这个PUBREL
报文需要被存储下来以便后续重传。 - 当接收方收到
PUBREL
报文,也可以确认在这一次的传输流程中不会再有重传的PUBLISH
报文到达,因此回复PUBCOMP
报文表示自己也准备好将当前的Packet ID
用于新的消息了。 - 当发送方收到
PUBCOMP
报文,这一次的QoS 2
消息传输就算正式完成了。在这之后,发送方可以再次使用当前的Packet ID
发送新的消息,而接收方再次收到使用这个Packet ID
的PUBLISH
报文时,也会将它视为一个全新的消息。
消息重复解决
在消息重复问题中不难发现重复问题的根源是对Packet ID
的使用没有达成共识,那么解决方案就是让通信双方正确同步地释放Packet ID
,也就是对Packet ID
的使用达成共识。QoS 2
中增加的PUBREL
和PUBCOMP
就是为了解决这个问题设置的。
QoS 2
规定,发送方只有在收到PUBREC
报文之前可以重传 PUBLISH
报文。一旦收到 PUBREC
报文并发出 PUBREL
报文,发送方就进入了Packet ID
释放流程,不可以再使用当前 Packet ID
重传 PUBLISH
报文。同时,在收到对端回复的 PUBCOMP
报文确认双方都完成 Packet ID
释放之前,也不可以使用当前 Packet ID
发送新的消息。(PUBREL
如果丢失也会进行重传)
QoS性能比较
通常 QoS 0
与 QoS 1
能够达到的吞吐比较接近,但 QoS 1
的 CPU 占用会略高于 QoS 0
,负载较高时QoS 1
的消息延迟也会进一步增加。而QoS 2
仅能达到QoS 1
一半的性能。
如何选择QoS
QoS 0
: 高频可容忍丢失的数据,如传感器数据
QoS 1
: 幂等操作的数据,或可以自行去重的场景
QoS 2
: 重要数据,不允许丢失,不允许重复