MQTT QoS 0、1、2:如何选择正确的投递等级
文章
2026年7月24日
6 分钟阅读
UllrAI

MQTT QoS 0、1、2:如何选择正确的投递等级

根据消息丢失与重复的业务后果、网络行为和端到端处理方式选择 MQTT QoS,而不是默认数字越大越安全。

MQTTQoS可靠性物联网架构

MQTT Quality of Service 描述一条消息在客户端与 Broker 之间如何投递。它不是整个系统的“质量评分”,数字更高也不代表一定更适合。

正确的选择始于业务问题:如果消息丢失、重复、延迟或被处理两次,会发生什么?

QoS 作用于每一跳

发布侧与订阅侧的 QoS 独立协商。

发布者可以用 QoS 1 发送,而订阅者只请求 QoS 0。Broker 会按该订阅可用的较低等级投递。同样,QoS 2 也无法让数据库事务或物理执行器天然只执行一次,它只约束当前 MQTT 网络链路。

应分别分析完整路径:

设备 → Broker → 消费者 → 数据库或执行器

每个箭头和每个处理步骤都有不同的失败方式。

MQTT QoS 0、1、2 报文交换时序图

上图只展示发布侧链路。Broker 会针对每个匹配的订阅者发起独立投递,实际等级不高于发布 QoS 与订阅 QoS 中的较低者。发布者收到确认,并不能证明订阅者、数据库、API 或执行器已经完成业务处理。

QoS 0:至多一次

QoS 0 发送 PUBLISH 后不等待确认。消息可能到达一次,也可能丢失。

它适合:

  • 高频数据;
  • 可由下一条新值替代;
  • 偶尔丢失成本很低;
  • 更看重低延迟和低开销。

例如每秒一次的温度、实时光标位置或高频调试指标。

QoS 0 并不等于“完全不可靠”。健康的 TCP 连接在保持连接时本身有顺序和可靠性。QoS 0 只是不会在连接中断后由 MQTT 继续恢复未完成的投递。

QoS 1:至少一次

QoS 1 要求接收 PUBACK。发布者未收到确认时可能重新发送,因此消费者必须允许重复消息。

QoS 1 很适合作为以下场景的默认起点:

  • 告警;
  • 状态变化;
  • 带幂等键的任务与指令;
  • 不希望出现缺口的测量;
  • 可以安全去重的事件。

载荷应支持幂等:

{
  "eventId": "01JZ8M7V2BR5H0R9BC2T2N2K0A",
  "deviceId": "pump-042",
  "type": "pressure.threshold.exceeded",
  "occurredAt": "2026-07-27T10:42:18Z",
  "value": 9.8,
  "unit": "bar"
}

消费者在执行副作用前记录 eventId。同一事件再次到达时,可以正常确认但不重复执行。

QoS 2:协议层恰好一次

QoS 2 使用四步交换:PUBLISHPUBRECPUBRELPUBCOMP。握手结束前,两端都要保存状态。

只有在以下条件成立时才值得承担这项成本:

  • 应用层很难安全处理重复;
  • Broker 和相关客户端都正确支持 QoS 2;
  • 已测量新增的延迟、带宽、内存和重连状态;
  • 确实需要 MQTT 当前链路的“恰好一次”。

QoS 2 仍无法让非事务副作用天然只执行一次。如果消费者启动电机后、保存完成状态前崩溃,MQTT 握手无法判断物理动作是否发生。业务幂等仍不可缺少。

可复现的 MQTT QoS 0、1、2 实验

RunMQTT QoS 实验把上述协议说明变成可重复的本地检查。实验固定使用 Eclipse Mosquitto 2.0.22、MQTT.js 5.15.2、MQTT 5,并为每次运行生成唯一 Topic。匿名 Broker 只绑定到 127.0.0.1:18883,它是用完即弃的测试设施,不是生产配置。

可以下载实验说明执行脚本Compose 文件Mosquitto 配置,也可以在仓库根目录直接执行:

docker compose -f public/examples/mqtt-qos-lab/compose.yaml up -d
pnpm mqtt:qos-lab
docker compose -f public/examples/mqtt-qos-lab/compose.yaml down

实测结果

以下观察记录于 2026-08-09,Node.js 版本为 24.14.0。它们验证报文流与失败行为,不是延迟、吞吐量或 Broker 对比基准。

场景观察结果能说明什么
正常 QoS 0 发布发布者发送 PUBLISH;订阅者收到 QoS 0当前链路没有 MQTT 确认
正常 QoS 1 发布发布者发送 PUBLISH 并收到 PUBACK;订阅者收到 QoS 1发布侧完成一次至少一次交换
正常 QoS 2 发布PUBLISHPUBRECPUBRELPUBCOMP;订阅者收到 QoS 2发布侧完成 QoS 2 状态机
持久会话订阅者离线离线时发布 QoS 0 和 QoS 1;恢复会话后只有 QoS 1 到达Broker 按当前持久会话配置排队了 QoS 1
消费者失败并重复发送事件 ID三次独立 QoS 1 发布、一次注入失败,最终业务副作用只执行一次稳定事件 ID 可以跨重试和重复保护业务副作用

最后一个场景刻意把同一业务事件发送三次。因为它们是三次独立发布,所以三个 MQTT DUP 标志都是 false,但载荷包含同一个 eventId。第一次处理在应用副作用前失败,第二次成功应用,第三次被去重。这说明业务幂等必须依赖稳定业务标识,不能依赖 MQTT DUP 标志。

另一轮针对一次性 RunMQTT Provider Core 的测试见托管 MQTT Broker 实测。它除了记录相同的 QoS 报文交换,还覆盖 TLS/WSS 连接耗时、持久会话恢复、Topic Policy 允许/拒绝行为和一轮受控 QoS 1 消息样本。

实验边界与失败注入

本地结果只证明这组固定客户端与 Broker 配置的行为,不能证明所有 Broker 都具有相同的队列上限、过期策略、持久化耐久性或重连行为。决定 QoS 前,应使用生产客户端版本与 Broker 设置重复实验。

MQTT.js 可能在异步业务处理提交数据库前,就已经确认收到 MQTT 报文。因此,处理器崩溃并不保证 Broker 一定重新投递。应把持久工作放入事务、Inbox 或幂等任务边界,并测试进程在接收后、提交前被终止的情况。测试网络重试时,应在发布者收到 PUBACK 前或 QoS 2 握手完成前中断连接,重连后再检查 Packet ID 和 DUP 标志。

MQTT QoS 对比:0、1 与 2

MQTT QoS 0 与 1 的主要取舍是恢复能力和开销。QoS 0 发送后不等待 MQTT 确认;QoS 1 会持续重试直到收到确认,因此消费者必须允许重复。消息丢失的影响大于重复到达时,应选择 QoS 1。

MQTT QoS 1 与 2 并不是“可靠”和“更可靠”的简单区别。QoS 1 使用两步报文交换并依赖应用幂等;QoS 2 使用四步协议握手,避免当前 MQTT 链路上的重复投递。QoS 2 仍无法让下游副作用恰好执行一次,因此多数生产系统使用 QoS 1 加事件 ID 或操作 ID,反而更简单也更完整。

下面的表格只能作为起点,最终选择还需在真实网络、消息速率、客户端库和 Broker 配置下验证。

消息建议起点原因
每秒传感器采样QoS 0下一条值可以替代丢失采样
最新状态同步QoS 1 + 保留消息状态重要,重复无害
告警事件QoS 1需要到达,可按事件 ID 去重
配置更新QoS 1 + 版本号只应用更新的版本
远程指令QoS 1 + 操作 ID + 过期时间避免过期与重复执行
财务或安全关键动作不应仅按表决定必须设计端到端事务与安全模型

QoS 与离线会话

根据 Broker 与会话设置,持久会话的订阅者离线时,QoS 1 或 2 消息可以排队。这很有用,但也可能在设备恢复后投递已经过时的工作。

指令消息应增加:

  • 消息过期时间;
  • 指令或操作 ID;
  • 目标设备身份;
  • 创建时间;
  • Schema 版本;
  • 确认主题或结果事件。

同时限制 Broker 队列。无限离线队列不是可靠性,而是没有边界的积压。

只应排队那些断线后仍有业务价值的消息。新的状态快照通常可以替代旧遥测,过期指令则应直接丢弃。需要同时测量单个客户端和整个设备群的重连积压,避免正常恢复过程压垮 Broker 或下游消费者。

测试失败,而不只是成功

有效的 QoS 测试应在关键时刻中断网络:

  1. 正常连接时发布;
  2. 确认返回前断线;
  3. 使用同一会话重连;
  4. 观察消息是否重试;
  5. 确认消费者正确处理重复;
  6. 确认过期指令不会执行。

还要测试大量客户端同时重连。单台设备很小的 QoS 状态,放大到设备群后可能非常可观。

推荐默认策略

高频、可替代的遥测从 QoS 0 开始;重要事件与指令从 QoS 1 开始。在考虑 QoS 2 前,先把消费者幂等做好。

关于 MQTT 5 的会话与过期变化,请阅读 MQTT 3.1.1 到 MQTT 5 迁移指南。使用主题设计指南明确权限与消息含义,并在生产设备启用持久会话前检查 MQTT 安全指南MQTT 教程提供了可直接执行的发布与订阅命令。公共 MQTT Broker适合一次性的连通性检查;要控制断线与会话条件,请使用上面的隔离本地实验。

把 MQTT 指南用于实践

先用一次性数据验证发布订阅链路,再跟随完整教程继续实践。