跳转到内容

QoS 2(恰好一次)

本节介绍 MQTT QoS 2(恰好一次送达)的完整握手协议、应用场景和性能特性。学习完成后,您将能够:

  • 理解 QoS 2 的四段握手流程
  • 掌握 PUBREC/PUBREL/PUBCOMP 的交互过程
  • 识别需要 QoS 2 的关键业务场景
  • 评估 QoS 2 对系统性能的影响

在开始本节之前,请确保:

  • 理解 QoS 0 和 QoS 1 的概念
  • 理解 QoS 级别选择对整个系统的影响
  • Mosquitto Broker 已运行

QoS 2(Exactly once,恰好一次)是 MQTT 中最高的可靠性级别,确保消息不会丢失也不会重复。

核心特点

  • 使用四段握手协议
  • 消息不会被重复送达
  • 最高可靠性保证
  • 最大网络开销和处理延迟
Publisher Broker Subscriber
│ │ │
│─── PUBLISH ──────────→│ │
│ (QoS=2, PacketID=1) │ │
│ │ │
│←─── PUBREC ───────────│ │
│ (PacketID=1) │ │
│ 收到记录确认 │ │
│ │ │
│─── PUBREL ───────────→│ │
│ (PacketID=1) │ │
│ 确认释放 │ │
│ │─── PUBLISH ──────────→│
│ │ (QoS=2, PacketID=1)│
│ │ │
│←─── PUBCOMP ──────────│←─── PUBREC ──────────│
│ (PacketID=1) │ (PacketID=1) │
│ 完成确认 │ │
│ │─── PUBREL ──────────→│
│ │ (PacketID=1) │
│ │ │
│ │←─── PUBCOMP ────────│
│ │ (PacketID=1) │
│ │ │
│ 双方完成确认 │ 双方完成确认 │
报文方向含义
PUBLISHP→B发布消息(QoS=2, PacketID)
PUBRECB→P收到发布(Publish Received),确认收到
PUBRELP→B释放发布(Publish Release),确认已处理
PUBCOMPB→P完成发布(Publish Complete),握手完成

场景 1:PUBREC 丢失

Publisher Broker
│ │
│─── PUBLISH ──────────→│
│ │
│←─── [PUBREC] ──✗─── │ PUBREC 丢失
│ │
│─── PUBLISH (重试) ───→│ 超时重发 PUBLISH
│ │
│←─── PUBREC ───────────│ Broker 识别重复,重发 PUBREC
│ │
│─── PUBREL ───────────→│ 正常继续
│ │
│←─── PUBCOMP ──────────│

场景 2:PUBREL 丢失

Publisher Broker
│ │
│ ←第一阶段完成→ │
│ │
│─── [PUBREL] ──✗────→│ PUBREL 丢失
│ │
│←─── PUBREC ───────────│ Broker 未收到 PUBREL,重发 PUBREC
│ │
│─── PUBREL (重试) ────→│ 重发 PUBREL
│ │
│←─── PUBCOMP ──────────│ 完成

发布者在 QoS 2 状态下需要维护的状态:

初始状态: 未发送
发送 PUBLISH: 等待 PUBREC
收到 PUBREC: 等待 PUBREL 确认
发送 PUBREL: 等待 PUBCOMP
收到 PUBCOMP: 完成(丢弃 PacketID)

Broker 需要在内存中维护所有进行中的 QoS 2 事务:

// Broker 内部 QoS 2 状态管理(概念)
class QoS2Transaction {
constructor(clientId, packetId, packet) {
this.clientId = clientId;
this.packetId = packetId;
this.packet = packet;
this.state = 'WAITING_PUBREL'; // 收到 PUBREC 后的状态
this.created = Date.now();
}
}
// 存储所有活跃的 QoS 2 事务
const activeTransactions = new Map();
function handlePubrec(clientId, packetId) {
// Broker 收到 PUBREL 前需保持状态
activeTransactions.set(
`${clientId}:${packetId}`,
new QoS2Transaction(clientId, packetId, packet)
);
}
场景推荐 QoS理由
支付交易QoS 2不可重复也不可丢失
库存计数更新QoS 2重复计数会导致库存错误
计费数据QoS 2必须精确记录每次使用
关键告警(火警/安防)QoS 2必须到达且只能一次
设备激活/注册QoS 2重复注册会导致系统错误
场景推荐 QoS理由
几乎所有通用场景QoS 0/1QoS 2 开销过大
高频传感器数据QoS 0不需要精确一次
设备状态更新QoS 1重复状态可接受
调试日志QoS 0丢失无关紧要
MQTT Out 节点配置:
├── Server: [MQTT Broker]
├── Topic: payment/transaction
├── QoS: 2 (Exactly once) ← 选择
├── Retain: false
└── Name: Payment Transaction
注意:Node-RED 默认的 MQTT 节点使用 QoS 2
仅在必要时修改为更低级别
Terminal window
# 发布 QoS 2 消息
mosquitto_pub -h localhost -t "critical/alarm" \
-q 2 -m '{"alarm": "fire", "zone": "A-12"}'
# 订阅 QoS 2
mosquitto_sub -h localhost -t "critical/alarm" \
-q 2 -v
# 验证四段握手(查看详细日志)
mosquitto_sub -h localhost -t "critical/alarm" \
-q 2 -v -d # -d 开启调试输出
指标QoS 0QoS 1QoS 2
消息交换次数1(单向)2(发+确认)4(两轮双向)
最小延迟< 1ms1-2ms3-5ms
吞吐量100%(基准)~40%~15%
带宽开销最低最高
发布者存储未确认消息完整握手状态
Broker 存储未确认消息完整握手状态
去重保证不适用
适用网络所有可靠网络极不可靠网络
QoS 2 的实际影响:
100 个设备,每秒发布 1 条消息(1KB payload):
QoS 0: ~100 KB/s 入站流量
QoS 1: ~150 KB/s (+100 PUBACK)
QoS 2: ~400 KB/s (+300 控制报文)
CPU 使用率(单核):
QoS 0: 5%
QoS 1: 15%
QoS 2: 40%
Terminal window
# 使用调试模式查看完整的 MQTT 协议交互
mosquitto_pub -h localhost -t "qos2/test" -q 2 -m "test" -d
# 预期输出:
# Client ... sending CONNECT
# Client ... received CONNACK
# Client ... sending PUBLISH (d0, q2, r0, m1, 'qos2/test', ... (4 bytes))
# Client ... received PUBREC (m1, rc=0)
# Client ... sending PUBREL (m1)
# Client ... received PUBCOMP (m1)
# 确认: 看到 PUBREC → PUBREL → PUBCOMP 的完整流程
  • 推荐: 只在必须保证不重不漏的关键场景使用 QoS 2
  • 推荐: 对 QoS 2 消息设置合理的超时和重试策略
  • 推荐: 监控 QoS 2 消息的完成率(过高未完成表示系统问题)
  • 避免: 在高吞吐场景中使用 QoS 2(降低系统性能)
  • 避免: 对传感器数据使用 QoS 2(QoS 0 已足够)
  • 避免: 在同一 Topic 中混用不同 QoS 级别

Q: 为什么不是所有消息都用最高可靠性?

A: 可以这样理解:QoS 级别就像快递服务的选择。

  • QoS 0:平邮——便宜、快速、但可能丢失
  • QoS 1:挂号信——有签收确认、保证送到、可能多跑一趟
  • QoS 2:专车配送——全程跟踪、精确送达一次、最贵最慢

对于温度数据这种每 3 秒更新一次的信息,用平邮(QoS 0)就足够了。只有控制设备或处理支付的指令才需要专车配送(QoS 2)。

本节要点总结:

  1. QoS 2 机制:四段握手(PUBLISH → PUBREC → PUBREL → PUBCOMP)
  2. 可靠性:最高级别,消息不会丢失也不会重复
  3. 性能开销:吞吐量约为 QoS 0 的 15%,延迟增加 3-5ms
  4. 适用场景:支付交易、库存计数、计费数据、关键告警
  5. 选择建议:仅在业务逻辑要求”不能多也不能少”时使用