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 基础上加入确认与重传机制。
基本流程:
- 发送方发送 QoS 1
PUBLISH,报文中携带Packet ID。 - 接收方收到后回复
PUBACK,并使用相同的Packet ID。 - 发送方收到
PUBACK后,认为本次投递完成,删除本地缓存。 - 如果发送方迟迟收不到
PUBACK,会重传PUBLISH。
4.2 为什么会重复
发送方没有收到 PUBACK 时,无法判断真实原因:
PUBLISH没有到达接收方。PUBLISH已到达,但PUBACK没有回到发送方。
第一种情况重传不会造成重复,因为接收方之前没收到。第二种情况重传会造成重复,因为接收方已经处理过第一次消息。
即使重传报文会设置 DUP=1,接收方也不能只凭 DUP 和 Packet ID 判断它一定是旧消息。原因是 Packet ID 在一次投递完成后会被重新使用,新的消息也可能因为重传而带有相同的 Packet ID 和 DUP=1。
结论:QoS 1 在协议层只能保证到达,不能保证不重复。
4.3 业务风险
如果业务没有去重,重复消息可能导致状态来回跳变。
典型问题:
- 发布方先发“开灯”,再发“关灯”。
- 订阅方实际收到顺序可能变成“开灯、关灯、开灯、关灯”。
- 对终端用户而言,设备状态会出现非预期变化。
4.4 适用场景
QoS 1 适合:
- 关键指令下发。
- 重要状态变更。
- 告警通知。
- 可以基于业务 ID、时间戳、递增序号做幂等或去重的业务。
使用 QoS 1 时,建议业务层显式设计去重或幂等:
- 消息携带唯一业务 ID。
- 消息携带单调递增序号。
- 消息携带时间戳,并和最近处理记录比较。
- 关键操作设计成幂等接口。
5. QoS 2:只交付一次
5.1 工作方式
QoS 2 通过四步握手解决 QoS 1 的重复问题。
基本流程:
- 发送方保存并发送 QoS 2
PUBLISH,等待接收方返回PUBREC。 - 接收方收到
PUBLISH后返回PUBREC。 - 发送方收到
PUBREC后,不再重传该PUBLISH,改为发送并保存PUBREL。 - 接收方收到
PUBREL后返回PUBCOMP。 - 发送方收到
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 0 | QoS 1 | QoS 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。
