Skip to content

MQTT QoS 0、1、2 的选择

来源:EMQX 博客 - MQTT QoS 0、1、2 解析:快速入门指南
原文作者:Zibo Zhou
原文日期:2024-10-13
整理说明:本文为结构化技术笔记,非原文逐字转载。

1. 核心结论

MQTT QoS 用于定义消息从发布方到订阅方的投递可靠性级别。QoS 等级越高,可靠性越强,但协议交互、状态维护和性能开销也越大。

QoS语义是否可能丢失是否可能重复典型代价适合场景
QoS 0最多交付一次最低高频、可容忍丢失的数据
QoS 1至少交付一次中等重要消息、可业务去重或可容忍重复
QoS 2只交付一次最高金融、航空、强一致关键操作

选择建议:

  • 能接受偶发丢失,优先 QoS 0。
  • 不能丢消息,但能处理重复,优先 QoS 1。
  • 不能丢,也不能重复,并能接受额外开销,再使用 QoS 2。

2. MQTT QoS 基本机制

QoS 是 MQTT 对消息投递质量的约束。发布者在 PUBLISH 报文中指定 QoS,Broker 转发给订阅者时通常会保留原 QoS。

但最终转发给订阅者的 QoS 可能被订阅端限制降级。例如:

  • 发布者发送 QoS 2 消息。
  • 订阅者订阅该主题时声明最大接收 QoS 为 1。
  • Broker 向该订阅者转发时,会将该消息降级为 QoS 1。

因此,完整链路中的实际 QoS 不只取决于发布方,也取决于订阅方声明的最大 QoS。

3. QoS 0:最多交付一次

3.1 工作方式

QoS 0 是最轻量的消息投递方式。发送方发送 PUBLISH 后不等待确认,也不保存消息用于重传。

特点:

  • 无确认报文。
  • 无重传机制。
  • 无 Packet ID。
  • 接收方不会收到协议层重传导致的重复消息。
  • 可靠性主要依赖底层 TCP 连接。

3.2 为什么可能丢消息

TCP 只能在连接稳定且未异常关闭的前提下保证传输可靠。如果连接关闭、重置,或者消息仍停留在网络链路、操作系统缓冲区中,就可能发生丢失。

所以 QoS 0 的主要风险不是“正常连接下随机丢”,而是连接异常时没有 MQTT 协议层补偿。

3.3 适用场景

QoS 0 适合:

  • 高频传感器数据。
  • 周期性状态上报。
  • 丢几条不影响整体业务判断的数据。
  • 对实时吞吐要求高、对单条消息完整性要求低的场景。

不适合:

  • 控制指令。
  • 交易、告警、审计类消息。
  • 丢失后业务无法恢复的消息。

4. QoS 1:至少交付一次

4.1 工作方式

QoS 1 在 QoS 0 基础上加入确认与重传机制。

基本流程:

  1. 发送方发送 QoS 1 PUBLISH,报文中携带 Packet ID
  2. 接收方收到后回复 PUBACK,并使用相同的 Packet ID
  3. 发送方收到 PUBACK 后,认为本次投递完成,删除本地缓存。
  4. 如果发送方迟迟收不到 PUBACK,会重传 PUBLISH

4.2 为什么会重复

发送方没有收到 PUBACK 时,无法判断真实原因:

  • PUBLISH 没有到达接收方。
  • PUBLISH 已到达,但 PUBACK 没有回到发送方。

第一种情况重传不会造成重复,因为接收方之前没收到。第二种情况重传会造成重复,因为接收方已经处理过第一次消息。

即使重传报文会设置 DUP=1,接收方也不能只凭 DUPPacket ID 判断它一定是旧消息。原因是 Packet ID 在一次投递完成后会被重新使用,新的消息也可能因为重传而带有相同的 Packet IDDUP=1

结论:QoS 1 在协议层只能保证到达,不能保证不重复。

4.3 业务风险

如果业务没有去重,重复消息可能导致状态来回跳变。

典型问题:

  • 发布方先发“开灯”,再发“关灯”。
  • 订阅方实际收到顺序可能变成“开灯、关灯、开灯、关灯”。
  • 对终端用户而言,设备状态会出现非预期变化。

4.4 适用场景

QoS 1 适合:

  • 关键指令下发。
  • 重要状态变更。
  • 告警通知。
  • 可以基于业务 ID、时间戳、递增序号做幂等或去重的业务。

使用 QoS 1 时,建议业务层显式设计去重或幂等:

  • 消息携带唯一业务 ID。
  • 消息携带单调递增序号。
  • 消息携带时间戳,并和最近处理记录比较。
  • 关键操作设计成幂等接口。

5. QoS 2:只交付一次

5.1 工作方式

QoS 2 通过四步握手解决 QoS 1 的重复问题。

基本流程:

  1. 发送方保存并发送 QoS 2 PUBLISH,等待接收方返回 PUBREC
  2. 接收方收到 PUBLISH 后返回 PUBREC
  3. 发送方收到 PUBREC 后,不再重传该 PUBLISH,改为发送并保存 PUBREL
  4. 接收方收到 PUBREL 后返回 PUBCOMP
  5. 发送方收到 PUBCOMP 后,认为本次 QoS 2 传输结束,双方可以释放并复用该 Packet ID

QoS 2 的核心不是简单多发几个确认包,而是让通信双方对 Packet ID 的释放时机达成一致。

5.2 为什么不会重复

QoS 1 的重复问题,本质来自双方对 Packet ID 是否已经释放缺少同步。接收方回复 PUBACK 后就可能释放 Packet ID,但发送方不一定已经收到确认。

QoS 2 用 PUBREL / PUBCOMP 增加了一段明确的释放流程:

  • 发送方收到 PUBREC 后,不能再用当前 Packet ID 重传原 PUBLISH
  • 在收到 PUBCOMP 前,发送方也不能用当前 Packet ID 发送新消息。
  • 接收方可以用 PUBREL 作为边界判断消息状态。

因此,QoS 2 可以在协议层区分“旧消息重传”和“新消息投递”,从而避免重复。

5.3 适用场景

QoS 2 适合:

  • 金融交易。
  • 航空、交通等强可靠行业。
  • 不能接受重复执行的控制流程。
  • 业务侧不想或不能实现可靠去重的关键消息。

限制:

  • 协议交互更长。
  • 服务端和客户端都需要维护更多状态。
  • 延迟和 CPU 成本更高。
  • 吞吐通常明显低于 QoS 0 / QoS 1。

6. 性能对比

以点对点通信为例,一般表现如下:

指标QoS 0QoS 1QoS 2
吞吐最高接近 QoS 0通常约为 QoS 0 / QoS 1 的一半
CPU 开销最低略高最高
延迟最低负载高时增加最高
协议状态最少需要保存待确认消息需要维护完整握手状态

实际性能仍会受 Broker 实现、网络环境、硬件规格、消息大小、持久化策略、客户端数量等因素影响。

7. QoS 选择决策表

业务要求推荐 QoS说明
数据高频上报,允许少量丢失QoS 0例如温湿度、周期采样值
消息必须到达,重复可接受QoS 1例如通知、状态同步
消息必须到达,重复不可接受,业务可去重QoS 1 + 业务幂等工业控制中更常见、更可控
消息必须到达,重复不可接受,业务不做去重QoS 2牺牲性能换协议层保证
网络不稳定但数据重要QoS 1 / QoS 2取决于是否可接受重复
对延迟极敏感QoS 0 / QoS 1避免默认上 QoS 2

8. 常见问题

8.1 QoS 1 如何去重?

QoS 1 的重复无法在协议层彻底避免,需要从业务层处理。

常用方案:

  • Payload 中增加唯一消息 ID。
  • 使用单调递增序号。
  • 使用时间戳,并只处理比上次更新更晚的数据。
  • 消费端保存最近处理过的消息 ID。
  • 下游操作设计为幂等。

工业物联网场景中,建议至少为关键控制指令增加 commandId,并在设备端或业务服务端保存执行状态。

8.2 QoS 2 消息什么时候向后分发?

为了降低延迟,Broker 或接收方可以在第一次收到 PUBLISH 时就启动向后分发。但之后在 PUBREL 前再次收到相同流程中的 PUBLISH 时,不能重复分发。

核心原则:可以尽早分发,但必须确保重复的 PUBLISH 不会触发重复业务处理。

8.3 是否应该默认使用 QoS 2?

不建议。

QoS 2 的语义最强,但成本也最高。很多业务用 QoS 1 加业务幂等更合适:

  • 协议交互更少。
  • 性能更好。
  • 去重逻辑可结合业务语义设计。
  • 更容易做审计、补偿和异常处理。

除非业务明确要求协议层“只交付一次”,否则不要为了“看起来更可靠”默认使用 QoS 2。

9. 工业物联网落地建议

消息类型建议 QoS备注
普通遥测数据QoS 0高频采样值可通过下一周期覆盖
重要遥测数据QoS 1例如低频关键指标、重要状态
告警事件QoS 1需要业务 ID 去重
设备控制指令QoS 1 + 幂等指令必须携带 commandId
计费、结算、交易类消息QoS 2 或 QoS 1 + 强业务事务取决于系统架构
设备影子 / 状态同步QoS 1消费端按版本号或时间戳覆盖

不要依赖 MQTT QoS 解决全部可靠性问题。更稳妥的设计通常还需要:

  • 消息唯一 ID。
  • 幂等消费。
  • 业务状态机。
  • 失败重试。
  • 操作审计。
  • 死信或异常消息处理。
  • 端到端确认机制。

10. 总结

MQTT QoS 不是越高越好,而是可靠性、重复风险、延迟、吞吐和实现复杂度之间的取舍。大多数工程场景优先考虑 QoS 0 / QoS 1,只有在明确不能丢、不能重且业务侧不适合去重时,再使用 QoS 2。

所有文章版权皆归博主所有,仅供学习参考。