MQTT协议之QoS0(<=1)、QoS1(>=1)、QoS2(=1)详解
目录
✅ QoS 0
1. “QoS 0:最多一次(At most once)”
2. “消息发布依赖于底层 TCP/IP 网络”
3. “即:<=1” 是什么意思?
底层实现细节(QoS 0)
总结
✅ QoS 1
底层实现机制(关键点)
1. 使用 Packet Identifier(包标识符)
2. 需要确认机制(PUBACK)
3. 基于 TCP,但增加应用层可靠性
QoS 1 消息传递流程(以发布者 → Broker 为例)
1、如果 PUBACK 丢失(或网络延迟):
端到端 QoS:发布者 ↔ Broker ↔ 订阅者
注意事项与最佳实践
总结
✅QoS 2
底层实现机制:四步握手(Four-Step Handshake)
1、详细交互流程(以 Client → Broker 为例)
2、关键状态管理:
3、如何防止重复?
端到端 QoS 与逐跳特性
注意事项与局限性
总结
📊 三种 QoS 对比总结
MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅消息传输协议,广泛用于物联网(IoT)场景。在 MQTT 中,QoS(Quality of Service,服务质量等级)定义了消息传递的可靠性级别。其中:
- QoS 0:最多一次(At most once)
- QoS 1:至少一次(At least once)
- QoS 2:恰好一次(Exactly once)
✅ QoS 0
1. “QoS 0:最多一次(At most once)”
- 这意味着消息可能送达一次,也可能完全不送达。
- 不会重复(因为没有重传机制),但可能丢失。
- 所以严格来说:“会发生消息丢失”,但不会发生重复。
❗注意:你原文说“会发生消息丢失或重复”——这是对 QoS 0 的常见误解。QoS 0 不会重复,只有 QoS 1 可能重复(因重传但未确认)。
2. “消息发布依赖于底层 TCP/IP 网络”
- MQTT 基于 TCP(可靠连接),但 QoS 0 不添加任何应用层确认或重传机制。
- 它只是把消息通过 TCP socket 发出去,就认为完成了。
- 如果 TCP 层成功传输,消息就送达;如果网络闪断、客户端崩溃、中间代理丢包等,应用层不会知道,也不会重发。
- 因此,其可靠性完全取决于 TCP 的当前状态。虽然 TCP 本身是可靠的(有重传、校验、顺序保证),但如果连接在发送前或发送中中断,QoS 0 消息就会丢失。
💡 补充:TCP 保证的是“已发送的数据可靠传输”,但 QoS 0 在调用 send() 后就不管了。如果 send() 成功写入内核缓冲区,但随后连接断开,数据可能根本没到对方——而发布者不知道。
3. “即:<=1” 是什么意思?
- 这里的 “<=1” 是一种非正式表达,意思是:
- 消息被接收的次数 小于或等于 1 次。
- 即:0 次(丢失) 或 1 次(成功),绝不会 >1 次(不重复)。
- 所以更准确的说法是:“最多一次交付”。
底层实现细节(QoS 0)
当客户端使用 QoS 0 发布消息时,具体流程如下:
-
构造 PUBLISH 报文:
- 固定报头(Fixed Header)中 QoS 字段设为
00(二进制)。 - 不包含 Packet Identifier(包标识符)(因为不需要确认)。
- 固定报头(Fixed Header)中 QoS 字段设为
-
通过 TCP socket 直接发送:
[PUBLISH] Topic: "sensor/temp"Payload: "25.3"QoS: 0→ 直接 write() 到 TCP socket。
-
无等待、无确认:
- 发布者发送后立即认为完成,继续执行后续代码。
- 不等待 PUBACK(因为 QoS 0 没有 PUBACK)。
- 代理(Broker)收到后直接转发给订阅者(同样用 QoS 0,除非订阅时指定了更高 QoS)。
-
失败情况:
- 若 TCP 连接在 send() 前已断开 → 消息根本发不出去。
- 若 send() 成功但网络随后中断 → 消息可能卡在网络中丢失。
- 无论哪种情况,发布者不会感知,也不会重试。
总结
| 特性 | QoS 0 |
|---|---|
| 交付保证 | 最多一次(At most once) |
| 是否可能丢失 | ✅ 是 |
| 是否可能重复 | ❌ 否 |
| 是否使用 Packet ID | ❌ 否 |
| 是否需要 ACK | ❌ 否 |
| 依赖底层 | 完全依赖 TCP 的瞬时可用性 |
| 适用场景 | 对可靠性要求不高、高频数据(如传感器读数)、节省带宽 |
MQTT 的 QoS 1(Quality of Service Level 1) 表示 “至少一次”(At least once) 的消息传递语义。
这意味着:
消息一定会被接收方收到,但可能会收到多次(即可能重复)。
下面我们从语义、底层实现机制、流程细节和注意事项几个方面详细解释 QoS 1。
✅ QoS 1
- 保证送达:发布者会确保消息最终被 Broker(或订阅者)收到。
- 允许重复:由于采用“发了就重试直到确认”的机制,如果确认(ACK)丢失,发布者会重发,导致接收方可能收到多份相同消息。
- 交付次数 ≥ 1:所以常写作 “≥1”。
📌 注意:QoS 1 不保证“恰好一次”,这是 QoS 2 的目标。
底层实现机制(关键点)
1. 使用 Packet Identifier(包标识符)
- 每个 QoS 1(或 QoS 2)的 PUBLISH 报文都包含一个 16 位的非零 Packet ID。
- 这个 ID 在同一连接内对发送方是唯一的(直到收到对应 ACK 后可复用)。
- 用于匹配请求与确认。
2. 需要确认机制(PUBACK)
- 发布者发送
PUBLISH(QoS=1)后,必须等待接收方返回PUBACK。 - 只有收到 PUBACK,发布者才认为该消息成功投递,可以释放该 Packet ID。
- 如果没收到 PUBACK(比如超时),发布者会重发相同的 PUBLISH 报文(含相同 Packet ID)。
3. 基于 TCP,但增加应用层可靠性
- 虽然底层是可靠的 TCP,但 TCP 无法解决“应用层是否处理了消息”的问题。
- QoS 1 在 MQTT 协议层面增加了“应用级 ACK”,确保逻辑上的送达。
QoS 1 消息传递流程(以发布者 → Broker 为例)
假设客户端 A 向 Broker 发布一条 QoS 1 消息:
[Client A] [Broker]| ||--- PUBLISH (QoS=1, PID=10) ->|| ||<-- PUBACK (PID=10) ----------|| |✅ 消息确认完成
1、如果 PUBACK 丢失(或网络延迟):
[Client A] [Broker]| ||--- PUBLISH (QoS=1, PID=10) ->| // 第一次发送| | // Broker 收到,处理,并发 PUBACK| X<-- PUBACK -------| // 但 ACK 丢了!| ||--- PUBLISH (QoS=1, PID=10) ->| // 超时后重发(相同 PID)| | // Broker 再次收到(重复)|<-- PUBACK (PID=10) ----------| // 再次回复 PUBACK| |✅ 最终确认,但 Broker 处理了两次!
💡 因此:接收方必须具备“去重”能力(如通过 Packet ID 或业务唯一 ID),否则会出现重复处理。
端到端 QoS:发布者 ↔ Broker ↔ 订阅者
MQTT 的 QoS 是**逐跳(hop-by-hop)**的,不是端到端的。
- 发布者到 Broker 的 QoS 由发布时指定。
- Broker 到订阅者的 QoS 取决于订阅时请求的 QoS 和发布 QoS 的较小值。
例如:
- 客户端发布 QoS 1 到主题
sensor/data - 订阅者订阅该主题时请求 QoS 2
- 实际投递给订阅者的 QoS = min(1, 2) = QoS 1
所以,即使订阅者想要更高可靠性,也不能超过发布者提供的 QoS。
注意事项与最佳实践
| 项目 | 说明 |
|---|---|
| 重复处理 | 应用层必须幂等(idempotent),能安全处理重复消息 |
| 资源开销 | 比 QoS 0 多一次往返(RTT),带宽略高(含 Packet ID 和 PUBACK) |
| 适用场景 | 需要可靠送达但可容忍重复的场景,如控制指令、告警通知 |
| 不要滥用 | 对高频传感器数据用 QoS 1 会显著增加网络和 Broker 负担 |
总结
QoS 1 通过“发送 + 等待 PUBACK + 超时重传”机制,确保消息至少被接收一次。但由于重传可能导致重复,接收方必须实现幂等处理。它在可靠性和效率之间取得了良好平衡,是实际应用中最常用的 QoS 级别之一。
MQTT 的 QoS 2(Quality of Service Level 2) 是最高级别的服务质量,提供 “恰好一次”(Exactly once) 的消息传递语义。
这意味着:
无论网络如何波动、重传多少次,接收方最终只会处理该消息一次,既不会丢失,也不会重复。
这是通过一个 四步握手协议(four-step handshake) 实现的,确保端到端的唯一交付。下面我们从原理、流程、实现细节和适用场景全面解析 QoS 2。
✅QoS 2
- 保证送达:消息一定会被接收。
- 禁止重复:即使在网络异常、ACK 丢失等情况下,也只处理一次。
- 交付次数 = 1 → “Exactly once”
- 开销最大:需要 4 条 MQTT 控制报文完成一次传输。
⚠️ 注意:QoS 2 的“恰好一次”是 在单个会话(Session)内、针对同一个 Packet ID 而言的。如果客户端断开重连且 Clean Session = true,则历史状态丢失,无法跨会话保证。
底层实现机制:四步握手(Four-Step Handshake)
QoS 2 使用 两个阶段的确认机制,涉及以下四种报文:
| 步骤 | 发送方 → 接收方 | 作用 |
|---|---|---|
| 1 | PUBLISH (QoS=2, DUP=0, PID=X) | 发起消息传输 |
| 2 | PUBREC (PID=X) | 接收方确认已收到,请求进入第二阶段 |
| 3 | PUBREL (PID=X) | 发送方确认可以释放消息 |
| 4 | PUBCOMP (PID=X) | 接收方最终确认,完成交付 |
这个过程类似于 两阶段提交(2PC),确保双方都达成一致。
1、详细交互流程(以 Client → Broker 为例)
假设客户端 A 向 Broker 发送一条 QoS 2 消息(Packet ID = 100):
[Client A] [Broker]| ||--- PUBLISH (QoS=2, PID=100) ->| // Step 1| ||<-- PUBREC (PID=100) ---------| // Step 2:Broker 收到并暂存| ||--- PUBREL (PID=100) -------->| // Step 3:Client 允许 Broker 处理| ||<-- PUBCOMP (PID=100) --------| // Step 4:Broker 确认处理完成| |✅ 消息“恰好一次”交付成功
2、关键状态管理:
- 发送方(Client):
- 发出 PUBLISH 后,必须保存该消息,直到收到 PUBCOMP。
- 收到 PUBREC 后,可丢弃原始 PUBLISH 内容,但需记住 PID 已进入第二阶段。
- 接收方(Broker):
- 收到 PUBLISH 后,不能立即投递给订阅者,而是先持久化并回复 PUBREC。
- 只有收到 PUBREL 后,才将消息投递出去,并回复 PUBCOMP。
💡 这种设计防止了因 ACK 丢失导致的重复处理。
3、如何防止重复?
QoS 2 通过 状态机 + Packet ID 去重 实现唯一性:
- 接收方维护“已接收但未完成”的 PUBLISH 列表(基于 PID)。
- 如果收到重复的 PUBLISH(DUP=1 或相同 PID),直接回复 PUBREC(不重新处理)。
- 只有在收到 PUBREL 后,才真正“消费”该消息,并标记为已完成。
- 已完成的 PID 在会话中不再接受。
因此,即使网络闪断、重传多次,消息逻辑上只被处理一次。
端到端 QoS 与逐跳特性
和 QoS 1 一样,MQTT 的 QoS 是 逐跳(hop-by-hop) 的:
- 发布者 → Broker:使用发布时指定的 QoS(如 QoS 2)
- Broker → 订阅者:使用 min(发布QoS, 订阅QoS)
❗ 所以,要实现端到端的 QoS 2,必须:
- 发布者用 QoS 2 发布
- 订阅者用 QoS 2 订阅
- Broker 支持 QoS 2(大多数都支持)
否则,中间某一段降级(如订阅者只订 QoS 1),则最终交付只是 QoS 1(至少一次)。
注意事项与局限性
| 项目 | 说明 |
|---|---|
| 性能开销大 | 4 次报文往返,延迟高,带宽占用多 |
| 内存/存储要求高 | 双方需缓存未完成的消息状态(PID + payload) |
| 不适用于高频数据 | 如每秒上千条传感器数据,QoS 2 会严重拖慢系统 |
| 会话依赖 | 若 Clean Session = true,重连后状态丢失,无法恢复未完成的 QoS 2 流程 |
| 并非万能 | “恰好一次”仅限协议层;若应用层处理失败,仍需业务补偿 |
总结
QoS 2 通过四步握手协议,在应用层实现了“恰好一次”的消息传递,彻底解决了丢失和重复问题。但它代价高昂,仅推荐用于对数据一致性要求极高的关键业务场景。在大多数物联网应用中,QoS 1 配合幂等处理已足够。
如果你正在设计系统,建议:
- 默认用 QoS 0(高效)
- 需要可靠但可去重 → QoS 1 + 幂等
- 绝对不能重复且不能丢 → QoS 2(慎用)
📊 三种 QoS 对比总结
| 特性 | QoS 0 | QoS 1 | QoS 2 |
|---|---|---|---|
| 语义 | 最多一次 | 至少一次 | 恰好一次 |
| 是否可能丢失 | ✅ 是 | ❌ 否 | ❌ 否 |
| 是否可能重复 | ❌ 否 | ✅ 是 | ❌ 否 |
| 报文数量 | 1 | 2(PUBLISH + PUBACK) | 4(PUBLISH → PUBREC → PUBREL → PUBCOMP) |
| 是否使用 Packet ID | ❌ | ✅ | ✅ |
| 适用场景 | 心跳、遥测 | 控制指令、告警 | 支付、订单、计费等关键操作 |
