消息消费失败,很多框架最直接解决方案就是自动重新发送,而重试就会产生重复消息。MQTT协议给出三种传递消息时能够提供的服务质量,服务质量其实就表示报文要分送几次。由低到高:

1 QoS 0:At most once(最多分发一次)

消息最多传递一次,即可为0或1次,若当时客户端不可用,则会丢失该消息。即消息在传递时,最多被送达1次。无消息可靠性保证,允许丢消息。

一种 "fire and forget" 的消息发送模式:Sender (Publisher 或 Broker) 发送一条消息后,就不再关心它有没有发送到对方,也不设置任何重发机制。

消息分发依赖底层网络能力。Publisher只会发布一次消息, Subscriber不会应答消息,Broker也不会储存和重发消息。该等级具有最高传输效率,但可能送达一次也可能根本没送达。

场景

一些对消息可靠性要求不太高的监控场景使用,如每s上报一次司机乘客地理位置,可接受数据少量丢失。

2 QoS 1:At least once

消息传递至少 1 次。消息在传递时,至少会被送达一次。即不允许丢消息,但允许重复消息。

包含简单的重发机制,Sender 发送消息之后等待 Subscriber的 ACK,若没收到 ACK,则重发消息。这种模式能保证消息至少能到达一次,但无法保证消息不重复。

MQTT 通过简单的 ACK 机制保证 QoS 1。Publisher会发布消息,并等待 Subscriber的 PUBACK 报文的应答,若规定时间内没收到 PUBACK 应答,Publisher会将消息的 DUP 置 1 并重发。 Subscriber接收到 QoS=1 的消息时,应回应 PUBACK 报文, Subscriber可能会多次接受同一个消息,无论 DUP 标志如何, Subscriber都会将收到的消息当作一个新的消息并发送 PUBACK 报文应答。

3 QoS 2:Exactly once

恰好一次。消息在传递时,只会被送达一次,不允许丢失、重复。设计了重发和重复消息发现机制,保证消息到达对方并且严格只到达一次。最高等级服务质量,消息丢失和重复都不可接受。使用该等级有额外开销。

Publisher发布 QoS=2 的消息后,会将发布的消息储存,并等待 Subscriber 回复 PUBREC 的消息,Publisher收到 PUBREC 消息后,它就可以安全丢弃掉之前的发布消息,因为它已知 Subscriber成功收到消息。Publisher会保存 PUBREC 消息并应答一个 PUBREL,等待 Subscriber回复 PUBCOMP 消息,当Publisher收到 PUBCOMP 消息后会清空之前所保存的状态。

当 Subscriber接收到一条 QoS=2 的 PUBLISH 消息时,他会处理此消息并返回一条 PUBREC 进行应答。当 Subscriber收到 PUBREL 消息后,它会丢弃掉所有已保存的状态,并回复 PUBCOMP。

无论在传输过程中何时出现丢包,发送端都负责重发上一条消息。不管发送端是 Publisher 还是 Broker,都是如此。因此,接收端也需要对每一条命令消息都进行应答。

4 QoS 在发布与订阅中的区别

MQTT 发布与订阅操作中的 QoS 代表不同含义:

  • 发布时的 QoS,消息发送到服务端时使用的 QoS

  • 订阅时的 QoS,服务端向自己转发消息时可使用的最大 QoS

  • 当客户端 A 的发布 QoS > 客户端 B 的订阅 QoS,服务端向客户端 B 转发消息时使用的 QoS 为客户端 B 的订阅 QoS

  • 当客户端 A 的发布 QoS < 客户端 B 的订阅 QoS,服务端向客户端 B 转发消息时使用的 QoS 为客户端 A 的发布 QoS。

不同情况下客户端收到的消息 QoS:

5 QoS 等级选型

QoS 级别越高,流程越复杂,系统资源消耗越大。应用程序可根据自己的网络场景和业务需求,选择合适级别。

QoS 0

  • 可接受消息偶尔丢失
  • 在同一个子网内部的服务间的消息交互或其他客户端与服务端网络非常稳定的场景

QoS 1

  • 对系统资源消耗较为关注,希望性能最优化
  • 消息不能丢失,但能接受并处理重复的消息

QoS 2

  • 不能忍受消息丢失(消息的丢失会造成生命或财产的损失),且不希望收到重复的消息
  • 数据完整性与及时性要求较高的银行、消防、航空等行业

大部分MQ都是At least once,如RocketMQ、RabbitMQ和Kafka,即MQ本身不保证消息不重复。

6 Kafka文档说支持Exactly once?

Kafka的确支持Exactly once,但Kafka “Exactly once”和消息传递服务质量标准中的“Exactly once”不同,它是Kafka提供另一特性,Kafka支持的事务也和通常事务有差异。Kafka事务和Excactly once主要为配合流计算。

既然已知MQ无法保证消息不重复,那就得消费代码接受“消息会重复”的事实,只能通过业务代码解决重复消息的业务副作用。

参考

  • [MQTT QoS(服务质量)介绍 | EMQ (emqx.com)](