本节摘要:把 MQTT 的三个服务质量等级 QoS 0/1/2 的报文流转与去重机制拆开——QoS0 最多一次、QoS1 至少一次、QoS2 恰好一次,并教会你在工程里怎么选。承接 3.2 的 QoS 位概念,通往 3.4 的会话与保留语义。
MQTT 明面上最值钱的设计,就是对"这条消息保证送到什么程度"给出三个档。很多初学着把 QoS 背成"0 不靠皮、1 至少一次、2 恰好一次",但只有把每一档在 Broker 和客户端之间怎么握手、怎么去重想清楚,才算真正会用。
要澄清一个常被误解的点:QoS 不是"发布方到发布方"的一锤子买卖,而是逐跳协商的。发布者 PUBLISH 进 Broker,这一跳有个 QoS;Broker 再投给订阅者,那一跳又有个 QoS(MIN(订阅时声明的, 发布时的))。所以同一份数据,对不同订阅者可能用不同力度投送。
QoS 0 的语义是最多送达一次,可丢。发送方发出 PUBLISH 后什么都不等,不设报文标识符、不收 ACK。它足够轻,也足够"裸"——适合实时遥测,丢一条刷新下一条就是了。代价是接收方无法得知消息 达没达。
QoS 1 保证至少送达一次,靠的是一来一回:
发布时间定义 PacketID=7,Broker 受理后回 PUBACK。若 ACK 在途中丢了,发布者会用 DUP 位标了重发;双方都可能"重复看到"这条消息,所以接收方要做去重——这正是 QoS 1 "至少一次、可能重复"的由来。
QoS 2 保证恰好一次,不会多不会少,代价是四段握手(PUBLISH→PUBREC→PUBREL→PUBCOMP):
关键在第二步 PUBREC 之后的"登记去重":Broker 一旦记为"已收",后续哪怕 PUBLISH 重到也只认一次,直到收到 PUBREL 才释放 PacketID 可复用。四段来回用"标识符 + 阶段登记"把重复彻底拒绝。
三档在"可靠性"和"开销/延迟"之间投票,没有绝对正确,只有合不合理:
| 档位 | 语义 | 可能遇到 | 握手 | 适用 |
|---|---|---|---|---|
| QoS 0 | 最多一次 | 丢消息 | 无 | 高频遥测、日志、位置刷新 |
| QoS 1 | 至少一次 | 重复 | PUBLISH+PUBACK | 状态上报、控制指令、告警 |
| QoS 2 | 恰好一次 | 绝不重不丢 | 四段握手 | 计费、命令、需严格幂等的场景 |
⚠️ 常见坑:QoS 2 自带"恰好一次"并不等于业务幂等——如果应用逻辑本身不幂等(如"加一"),重复触发仍会错账。可靠投送只是指报文,业务幂等还得靠应用层设计。
QoS>0 都依赖 PacketID 对齐收发。这是一个 16 位数字,客户端和 Broker 各维护一套,边用边递增、用完回绕。上面 QoS 2 四步都围绕同一个 PacketID 打转,标识符一旦被占用未释放,就退不回去了。
# 概念化:QoS2 围绕 PacketID 的两个状态 packetid = 9 被占 PUBLISH(9) → Broker 登记 已见过 PUBREC(9) → 发布者得知已登记 PUBREL(9) → Broker 释放占用 PUBCOMP(9) → 双向完成 可复用 未收 PUBREL 前, PacketID=9 不得复用给别的消息
给你一个可复用的判定顺口溜:能重发的降一档、不能接受的升一档、本就无意义的直接 0。温度刷新丢了就丢 → QoS 0;风扇开关指令"至少收到一次" → QoS 1;商品计费/防重复触发命令 → QoS 2。剩下的功夫花在订阅端去重与应用幂等上,往往比一味上 QoS 2 更划算。
很多人记得三档的语义,却在"谁负责去重、协作几方、是否有冗余"上模糊。下面这张表把每一档的分工责任钉死,方便对号排查:
| 项 | QoS 0 | QoS 1 | QoS 2 |
|---|---|---|---|
| 报文标识符 | 无 | 有 | 有 |
| 是否等待应答 | 否 | 等 PUBACK | 等 PUBREC/PUBREL/PUBCOMP |
| 重复可能性 | 可丢 | 可能重复 | 绝不重复 |
| 去重责任 | 无 | 收方自行去重 | 接收方登记去重 |
| 典型代价 | 最低 | 中 | 最高(四段 + 状态登记) |
| 面试级判据 | 火了就火了 | 至少一次 | 恰好一次 |
排障时若发现"某消息被收到多次",先别怪 Broker,按表查:是不是用了 QoS 1 且收方没做去重?是不是重发时 DUP 位没被应用层利用?把责任表一翻,"多份"和"丢失"两个老大难就能各自归位。
对 QoS 1/2,无论丢在哪一段(PUBLISH 丢了、ACK 丢了、还是 PUBREL 丢了),统一的救济手段只有两个字:重发。区别只在重发后对方怎么表态——QoS 1 重发一次即可(对方再回 PUBACK),QoS 2 则必须让双方状态都回到"已登记/已释放"扒平。能否在工程里正确补齐"超时重发 + 标识符对齐",决定了这套可靠机制是"越用越稳"还是"越用越乱"。
一个实操习惯:客户端上报路径上,把 QoS 与"幂等字段"绑定——QoS 1/2 的载荷里尽量带一个业务侧去重键(如"时间戳 + 设备号"),这样哪怕协议层重复,业务侧也能用同一把钥匙判重。这把"报文可靠 + 业务幂等"的防线补完,是我在大量项目里宁可多做也不省的一步。
下一节盘点 MQTT 让通信更有"状态感"的高级特性:保留消息、遗嘱与会话。