MQTT 服务质量 (QoS) 深度解析
什么是 QoS?为什么它很重要?
想象一下寄信,你有几种选择:
平信 (Postcard): 丢进邮筒就不管了,可能寄到,也可能寄丢。这是最快、最便宜的方式。
挂号信 (Registered Mail): 需要收件方签收,邮局保证至少会送到对方手上,如果没收到签收回执,会重新投递。对方可能会因为某些原因(比如回执丢失)收到两次。
机密文件专送 (Secure Courier): 一个非常严谨的过程。快递员送出文件,对方签收表示“已收到但未开启”,快递员回报总部。总部再授权快递员让对方“开启文件”,对方开启后再次签字确认“已开启并处理”,快递员再次回报总部。整个过程确保文件只被对方精确地接收和处理一次。
MQTT 的 QoS (Quality of Service) 就扮演着类似的角色。它不是指网络速度有多快,而是指 消息投递的可靠性承诺。根据你的业务需求,你可以在“快但不一定可靠”和“绝对可靠但慢一点”之间做出选择。
QoS 0: 最多一次 (At most once) - “发射后不管”
这是最基础的等级,就像寄平信。
核心机制
发布者 (Publisher): 发送消息 (PUBLISH)。
代理 (Broker): 接收消息并尝试发送给订阅者。
订阅者 (Subscriber): 接收消息。
整个过程没有任何确认环节。发布者把消息发出去后,就认为任务完成了,不会关心对方是否收到,也不会重发。
场景与数据讲解
场景: 智能家居中的一个温度传感器,每 5 秒向家庭控制中心报告一次客厅温度。
设备:
发布者: 客厅温度传感器 (
sensor-livingroom
)订阅者: 家庭控制 App (
app-control-center
)主题:
home/livingroom/temperature
数据:
{"temperature": 22.5, "timestamp": 1677610000}
情况一:网络正常
sensor-livingroom
发送 PUBLISH 消息,内容为{"temperature": 22.5, ...}
。Broker 收到消息。
Broker 将消息转发给
app-control-center
。App 成功收到温度数据,更新界面显示。
[传感器] --- PUBLISH ---> [Broker] --- PUBLISH ---> [App]
(发送后即忘)
情况二:网络瞬间中断
sensor-livingroom
发送 PUBLISH 消息。在消息到达 Broker 之前,网络出现波动,数据包丢失。
sensor-livingroom
不知道消息丢失了,它不会做任何事。Broker 从未收到该消息,因此
app-control-center
也永远不会收到22.5
这条温度读数。5 秒后,传感器发送下一条数据,例如
{"temperature": 22.6, ...}
,一切照常。
优点: 速度极快,网络和设备开销最小。因为它只发送一次数据包。
缺点: 消息可能会丢失。
适用场景:
可以容忍数据丢失的非关键业务。
数据更新频率非常高,丢失一两条无伤大雅(如上面的温度传感器)。
网络环境非常稳定可靠。
QoS 1: 至少一次 (At least once) - “必须收到回执”
这个等级保证消息至少会送达一次,就像寄挂号信。
核心机制
为了确保送达,QoS 1 引入了一个确认机制:PUBACK
(Publish Acknowledgment)。
发布者 (Publisher): 发送
PUBLISH
消息,并在本地保存该消息的副本,直到收到确认为止。接收方 (Broker/Subscriber): 收到消息后,必须回复一个
PUBACK
确认包。重发机制: 如果发布者在一定时间内没有收到
PUBACK
,它会认为消息丢失了,于是会重新发送该消息的副本(此时消息头中的 DUP 标志位会被设为 1,表示这是一个重复的消息)。
场景与数据讲解
场景: 你通过手机 App 给家里的智能电灯发送一个“开灯”指令。
设备:
发布者: 你的手机 App (
app-remote-control
)订阅者: 客厅的智能电灯 (
smart-light-01
)主题:
home/livingroom/light/command
数据:
{"action": "ON"}
情况一:网络正常
App 发送
PUBLISH
指令{"action": "ON"}
,并启动一个计时器等待确认。Broker 收到消息,并立即回复一个
PUBACK
给 App。App 收到
PUBACK
,知道 Broker 已成功接收,于是删除本地保存的指令副本。同时,Broker 将指令转发给智能电灯(这个过程同样遵循 QoS 1)。
电灯收到指令,执行开灯动作,并回复一个
PUBACK
给 Broker。Broker 收到电灯的
PUBACK
,整个流程完成。
[App] --- PUBLISH (保存副本) ---> [Broker]
[App] <--- PUBACK --- [Broker] (收到确认, 删除副本)[Broker] --- PUBLISH (保存副本) ---> [电灯]
[Broker] <--- PUBACK --- [电灯] (收到确认, 删除副本)
情况二:确认包 (PUBACK) 丢失
这是 QoS 1 可能产生重复消息的关键点。
App 发送
PUBLISH
指令{"action": "ON"}
。Broker 成功收到,并将指令转发给电灯。
电灯收到指令,成功开灯。
电灯向 Broker 回复
PUBACK
。不幸的是,这个
PUBACK
在传输过程中丢失了。Broker 没有收到电灯的确认,它的计时器超时了。
Broker 重新发送
PUBLISH
指令{"action": "ON"}
(DUP 标志位为 1)。电灯再次收到“开灯”指令。对于一个简单的电灯,它可能会再次执行“开灯”动作(虽然灯已经是亮的),或者忽略这个重复指令。
这次电灯回复的
PUBACK
成功到达 Broker,流程结束。
优点: 保证消息不会丢失,可靠性高。
缺点: 可能会收到重复的消息。订阅方需要有能力处理重复数据(例如,通过消息 ID 来判断是否已处理过)。
适用场景:
绝大多数要求可靠传输的场景。
远程控制指令,如开关、调节设备状态。重复执行一次通常没有严重副作用。
重要的状态通知,如“门已打开”、“设备离线”。
QoS 2: 正好一次 (Exactly once) - “严谨的四步握手”
这是最可靠但也最慢的等级,通过一个精密的四步握手协议来确保消息不重不丢,就像护送机密文件。
核心机制
QoS 2 不仅要确认对方“收到”,还要确认对方“处理”了这个消息。
PUBLISH
: 发布者发送消息,并在本地保存。PUBREC
(Publish Received): 接收方收到后,回复PUBREC
。这相当于说:“我收到你的消息了,我把它存起来了,请放心。现在请告诉我是否可以发布/处理它。”PUBREL
(Publish Release): 发布者收到PUBREC
后,就可以丢弃本地的消息副本了,然后回复一个PUBREL
。这相当于说:“好的,既然你收到了,现在请正式发布/处理它吧。”PUBCOMP
(Publish Complete): 接收方收到PUBREL
后,才将消息传递给订阅者,并回复PUBCOMP
。这相当于说:“好的,我已经处理完毕。” 发布者收到PUBCOMP
后,整个流程才算真正结束。
场景与数据讲解
场景: 一个在线支付系统通过 MQTT 发送一条支付指令,要求从账户 A 扣款 100 元。这条指令绝对不能丢失,也绝对不能被执行两次。
设备:
发布者: 支付网关 (
payment-gateway
)订阅者: 银行核心系统 (
bank-core-system
)主题:
billing/transaction
数据:
{"from_account": "A", "to_account": "B", "amount": 100}
流程演示(网络一切正常)
PUBLISH
:payment-gateway
发送扣款指令,并在数据库中将该条指令标记为“处理中”。PUBREC
: Broker 收到后,将消息持久化存储,并回复PUBREC
。PUBREL
:payment-gateway
收到PUBREC
后,更新指令状态为“已确认送达”,并发送PUBREL
。PUBCOMP
: Broker 收到PUBREL
后,才将消息推送给bank-core-system
,并回复PUBCOMP
。bank-core-system
执行扣款,确保只执行一次。
--- 第1,2步:确认送达 ---
[支付网关] --- PUBLISH (保存消息) ---> [Broker]
[支付网关] <--- PUBREC (收到, Broker存储消息) --- [Broker]--- 第3,4步:确认处理 ---
[支付网关] --- PUBREL (删除本地消息) ---> [Broker]
[支付网关] <--- PUBCOMP (Broker处理完成) --- [Broker]
为什么不会重复?
关键在于 PUBREC
和 PUBREL
这两步。Broker 只有在收到 PUBREL
之后,才会真正将消息投递给订阅者。即使 PUBREL
丢失了,发布者会重发 PUBREL
,Broker 收到重复的 PUBREL
也知道这是同一个流程,不会导致消息被投递两次。这个双重确认机制保证了消息的唯一性。
优点: 精确一次,既不丢失也不重复,可靠性最高。
缺点: 流程最复杂,通信往返次数最多(四次),因此性能开销最大,速度最慢。
适用场景:
绝对不允许数据重复或丢失的场景。
计费、支付系统。
关键的、不可逆的业务指令。
总结对比
特性 | QoS 0 (最多一次) | QoS 1 (至少一次) | QoS 2 (正好一次) |
---|---|---|---|
可靠性 | 最低 | 中等 | 最高 |
消息传递保证 | 不保证 | 保证至少一次 | 保证正好一次 |
消息丢失可能 | 是 | 否 | 否 |
消息重复可能 | 否 | 是 | 否 |
网络开销 | 最小 (1次传输) | 中等 (2次传输) | 最大 (4次传输) |
性能/速度 | 最快 | 较快 | 最慢 |
核心机制 | 发射后不管 |
|
|
适用场景 | 传感器数据、可丢弃的日志 | 远程控制、重要状态通知 | 计费、交易、关键业务指令 |
希望这份结合了具体场景和数据流的详细讲解,能帮助你彻底理解 MQTT QoS 的工作原理和选择依据。