0%

MQTT QoS介绍

MQTT 提供了 QoS 机制,其核心是设计了多种消息交互机制来提供不同的服务质量,来满足用户在各种场景下对消息可靠性的要求

MQTT QoS

在网络受限的环境下,MQTT如果只依靠底层的 TCP 传输协议,并不能完全保证消息的可靠到达。因此,MQTT 提供了 QoS 机制,其核心是设计了多种消息交互机制来提供不同的服务质量,来满足用户在各种场景下对消息可靠性的要求。

QoS由发布者在PUBLISH报文中指定,消息的 QoS 等级可能会在转发的时候发生降级(比如订阅者在订阅时要求 Broker 可以向其转发的消息的最大 QoS 等级为 QoS 1,那么后续所有 QoS 2 消息都会降级至 QoS 1 转发给此订阅者,而所有 QoS 0 和 QoS 1 消息则会保持原始的 QoS 等级转发)

1727007541789.png

QoS 0

QoS 0 消息可靠性依赖完全底层TCP协议,消息即发即弃,不需要等待确认,不需要存储和重传,因此对于接收方来说,永远都不需要担心收到重复的消息。

消息丢失问题

TCP只能保证连接不关闭的情况下消息的可靠性,在连接关闭、重置等情况下会导致消息丢失。

1727007618068.png

QoS 1

QoS 1 加入了应答与重传机制,发送方只有在接收到PUBACK后才能认为消息投递成功, 在此之前发送方需要存储该PUBLISH报文以便下次重传,QoS 1采用Packet ID来标示报文,PUBACK需要采用和PUBLISH报文一致的Packet ID

1727007959670.png

重复消息问题

但是QoS 1可能会造成重复消费消息问题,PUBLISH已经到达接收方,但接收方的PUBACK没有传回给发送方,发送方可能触发重传,导致重复消息。

1727015533394.png

虽然重传时 PUBLISH 报文中的 DUP 标志会被设置为 1,用以表示这是一个重传的报文。但是接收方并不能因此假定自己曾经接收过这个消息,仍然需要将其视作一个全新的消息。因为对于接收方来说,可能存在以下两种情况:

  1. 第一种情况,发送方由于没有收到 PUBACK 报文而重传了 PUBLISH 报文。此时,接收方收到的前后两个 PUBLISH 报文使用了相同的 Packet ID,并且第二个 PUBLISH 报文的 DUP 标志为 1,此时它确实是一个重复的消息。

  2. 第二种情况,第一个 PUBLISH 报文已经完成了投递,1024 这个 Packet ID 重新变为可用状态。发送方使用这个 Packet ID 发送了一个全新的 PUBLISH 报文,但这一次报文未能到达对端,所以发送方后续重传了这个 PUBLISH 报文。这就使得虽然接收方收到的第二个 PUBLISH 报文同样是相同的 Packet ID,并且 DUP 为 1,但确实是一个全新的消息。

1727015789844.png

由于接收方无法区分这两种情况,所以只能将这些 PUBLISH 报文都当作全新的消息来处理。因此当我们使用 QoS 1 时,消息的重复在协议层面上是无法避免的。

可以基于QoS 1 自行去重,如在报文中增加时间戳或单调计数器,接收方根据计数关系判断是否是一个新消息

QoS 2

QoS 2 解决了消息丢失和消息重复问题,但也带来了很大的开销,因为每一次QoS 2 消息传递,至少要进行两次请求响应流程。

1727015897624.png

流程如下:

  1. 发送方存储并发送 QoS 2PUBLISH 报文,然后等待接收方回复 PUBREC 报文。
  2. 当发送方收到 PUBREC 报文,即可确认对端已经收到了PUBLISH报文,发送方将不再需要重传这个报文,并且也不能再重传这个报文。此时发送方可以删除本地存储的 PUBLISH 报文,然后发送一个 PUBREL 报文,通知对端自己准备将本次使用的 Packet ID 标记为可用了。与 PUBLISH 报文一样,我们需要确保 PUBREL 报文到达对端,所以也需要一个响应报文,并且这个 PUBREL 报文需要被存储下来以便后续重传。
  3. 当接收方收到 PUBREL 报文,也可以确认在这一次的传输流程中不会再有重传的 PUBLISH 报文到达,因此回复 PUBCOMP 报文表示自己也准备好将当前的 Packet ID 用于新的消息了。
  4. 当发送方收到 PUBCOMP 报文,这一次的 QoS 2 消息传输就算正式完成了。在这之后,发送方可以再次使用当前的Packet ID发送新的消息,而接收方再次收到使用这个 Packet IDPUBLISH 报文时,也会将它视为一个全新的消息。

消息重复解决

消息重复问题中不难发现重复问题的根源是对Packet ID的使用没有达成共识,那么解决方案就是让通信双方正确同步地释放Packet ID,也就是对Packet ID的使用达成共识。QoS 2中增加的PUBRELPUBCOMP就是为了解决这个问题设置的。

QoS 2 规定,发送方只有在收到PUBREC报文之前可以重传 PUBLISH 报文。一旦收到 PUBREC 报文并发出 PUBREL 报文,发送方就进入了Packet ID释放流程,不可以再使用当前 Packet ID 重传 PUBLISH 报文。同时,在收到对端回复的 PUBCOMP 报文确认双方都完成 Packet ID 释放之前,也不可以使用当前 Packet ID 发送新的消息。(PUBREL如果丢失也会进行重传)

QoS性能比较

通常 QoS 0QoS 1 能够达到的吞吐比较接近,但 QoS 1 的 CPU 占用会略高于 QoS 0,负载较高时QoS 1 的消息延迟也会进一步增加。而QoS 2 仅能达到QoS 1 一半的性能。

如何选择QoS

QoS 0: 高频可容忍丢失的数据,如传感器数据

QoS 1 : 幂等操作的数据,或可以自行去重的场景

QoS 2 : 重要数据,不允许丢失,不允许重复