MQTT 的 QoS 0、1、2 怎么选:重复与丢失之间的取舍
QoS 不是数字越大越好。本文说明三个等级各自的真实语义、QoS 1 为什么会产生重复消息、以及为什么大多数遥测场景用 QoS 1 加业务去重比用 QoS 2 更合适。
- QoS 不是可靠性滑块,等级越高设备维持的状态和交互次数越多。
- QoS 1 下出现重复消息是协议预期行为,不是故障,接收方必须自己去重。
- 周期性遥测丢一条影响有限,用 QoS 0 或 1 即可,不必上 QoS 2。
- QoS 2 的「恰好一次」只在单次会话内成立,设备重启后该保证不再存在。
- 希望离线期间的下行命令重连后送达,必须保留会话并设置会话过期时间。
几乎每个物联网项目都会在某个时刻讨论 QoS 该设成几。常见的误解是把它当成可靠性的滑块 —— 调到 2 就最可靠。实际上三个等级解决的是不同问题,而等级越高,设备需要维持的状态和交互次数也越多,在电池供电或窄带场景下这是实打实的代价。
三个等级的真实语义
- QoS 0(至多一次):发出去就不管了,不确认、不重传。网络抖动时消息直接丢失,且发送方不会知道;
- QoS 1(至少一次):发送方等待确认,没收到就重发。消息一定会到,但可能到多次;
- QoS 2(恰好一次):通过四次握手保证既不丢也不重,代价是每条消息多出三次往返。
需要强调 QoS 1 的「至少一次」是字面意思。确认报文丢失时,发送方无法区分「对方没收到」和「对方收到了但确认丢了」,只能重发。所以 QoS 1 下出现重复消息不是故障,是协议的预期行为,接收方必须自己处理。
为什么多数场景选 QoS 1
周期性遥测数据有一个天然特点:下一条很快就会来。偶尔丢一条温度读数,影响通常小于为此付出的协议开销,这类场景 QoS 0 就够。而需要确保到达的消息 —— 告警、状态变更、命令响应 —— 用 QoS 1,再在业务层用消息标识去重,总开销明显低于 QoS 2。
业务层去重的做法很简单:设备为每条消息生成一个在自己范围内单调递增的序号,平台按「设备标识 + 序号」判重,已处理过的直接丢弃。这个判断本来在幂等性设计里就要做,并不是为 QoS 额外增加的工作。
QoS 2 真正适用的情况
当重复处理一条消息会造成不可逆后果、而业务侧又确实无法去重时,才值得用 QoS 2。典型是计费类的累加操作,或者会触发物理动作且不能重复执行的命令。但在这类场景里,更稳妥的做法往往是在业务协议里加入请求标识并让接收方幂等,而不是依赖传输层的恰好一次 —— 因为「恰好一次」只在单次 MQTT 会话的范围内成立,设备重启、会话过期之后这个保证就不存在了。
会话保持与离线消息
QoS 1 和 2 的重传依赖会话状态。如果设备连接时把清理会话标志设为真,Broker 会丢弃之前的会话,未确认的消息也随之消失。希望设备离线期间的下行命令在重连后仍然送达,就必须保留会话,并在 MQTT 5.0 中设置合适的会话过期时间。这一项常被忽略,表现是「命令偶尔不执行」,而排查方向往往跑偏到网络上。
顺带一提,离线消息会占用 Broker 的存储。设备长期离线且下行频繁时,积压的消息可能在重连瞬间一起涌向设备,造成设备处理不过来而再次断开。会话过期时间与离线消息上限这两个参数,应当在物联网平台解决方案的容量设计里一并确定。QoS 的取舍最终体现在设备功耗与平台负载上,这两项应当在物联网平台解决方案的容量设计里一并核算。
常见问题
MQTT QoS 1 为什么会收到重复消息?
确认报文丢失时,发送方无法区分接收方没收到、还是收到了但确认在回程中丢失,因此只能重发。这是 QoS 1「至少一次」语义的直接结果。接收方应当按消息中的业务序号或唯一标识去重,而不是期待传输层不重复。
电池供电设备该用哪个等级?
优先 QoS 0,确有必要送达的消息才用 QoS 1。每提高一级都意味着更多的往返和更长的射频开启时间,对电池寿命的影响通常比预期大。实际取值应当在真机上测功耗后再定。
QoS 能保证消息顺序吗?
同一个客户端、同一个 QoS 等级下,协议要求按序投递;但不同 QoS 等级之间、以及重连之后,顺序不应被依赖。需要严格顺序时,在消息体里带上序号由业务侧判断。
参考资料
- MQTT Version 5.0 OASIS Standard · OASIS
- MQTT - The Standard for IoT Messaging · MQTT.org