2. MQTT 基础回顾

Sparkplug 并不是一套全新的通信协议,它建立在 MQTT 3.1.1 / 5.0 之上,通过约定主题命名空间、载荷编码和状态机,把“通用消息总线”约束成“工业设备状态总线”。因此,理解 Sparkplug 的设备状态管理,必须先回到 MQTT 的五个核心机制:发布/订阅模型、主题、QoS、保留消息和遗嘱消息。它们分别解决了“谁和谁通信”“消息发到哪里”“消息可靠到什么程度”“新订阅者如何立刻拿到状态”“设备掉线如何被感知”这五个问题。

一、发布/订阅模型:解耦是状态管理的前提

MQTT 采用发布/订阅(Pub/Sub)模型,而不是 HTTP 那样的请求/响应模型。发布者(Publisher)把消息发送给 Broker,订阅者(Subscriber)向 Broker 订阅自己关心的主题。发布者与订阅者彼此不知道对方的存在,也不需要同时在线。这种解耦带来三个直接好处:

  • 空间解耦:设备只连 Broker,不需要知道 SCADA、MES、云平台各自的地址。
  • 时间解耦:设备上报时,上层应用可以不在线;应用上线后可通过保留消息补齐最新状态。
  • 一对多分发:一条设备状态消息可同时被多个订阅者消费,无需设备重复发送。

在 Sparkplug 中,边缘节点(Edge Node)发布自己的状态,主应用(Primary Host Application)订阅整个 Sparkplug 命名空间。Broker 成为唯一的状态汇聚点,这正是“设备状态管理”能够集中化的基础。

二、主题:Sparkplug 的命名空间骨架

主题(Topic)是 MQTT 的消息路由地址,使用斜杠分层,支持 + 单层通配和 # 多层通配。普通 MQTT 主题可以任意命名,而 Sparkplug 强制规定了命名空间:

spBv1.0/<group_id>/<message_type>/<edge_node_id>/[<device_id>]

示例:
spBv1.0/LineA/NBIRTH/PLC01
spBv1.0/LineA/NDATA/PLC01
spBv1.0/LineA/DDATA/PLC01/Sensor07
spBv1.0/LineA/NDEATH/PLC01

其中 spBv1.0 是 Sparkplug B 版本命名空间,group_id 用于逻辑分组,message_type 决定消息语义(NBIRTH、NDATA、NDEATH、DBIRTH、DDATA、DDEATH、STATE 等)。主题不再只是“地址”,而是承载了消息类型和层级关系,使订阅者可以只订阅 spBv1.0/LineA/# 就获得整条产线的状态流。

三、QoS:状态消息的可靠等级

MQTT 定义三个服务质量等级,决定消息投递的保证程度:

QoS语义交互次数典型用途
0最多一次,可能丢失PUBLISH高频遥测、可容忍丢点
1至少一次,可能重复PUBLISH / PUBACK设备状态、告警
2恰好一次,开销最大四次握手计费、关键指令

Sparkplug 规范建议状态类消息(NBIRTH、NDEATH、DBIRTH、DDEATH)使用 QoS 1,数据类消息(NDATA、DDATA)也通常使用 QoS 1。原因是状态管理更怕“丢”而不是“重”:重复消息可以通过 bdSeq、seq 序号去重,但丢失的 BIRTH 或 DEATH 会导致上层状态长期错误。

QoS 是端到端中“每一跳”的保证,Broker 与订阅者之间仍按订阅时协商的 QoS 投递。发布 QoS 1、订阅 QoS 0,最终仍可能丢消息。

四、保留消息与遗嘱消息:状态快照与掉线感知

保留消息(Retained Message):发布时设置 retain 标志,Broker 会为该主题保存最后一条保留消息。新订阅者一旦订阅匹配主题,立即收到这条消息,无需等待下一次发布。Sparkplug 利用这一机制,让主应用启动后能立刻获得各节点的最新 BIRTH 证书,而不必轮询或等待设备重发。

遗嘱消息(Will Message):客户端在 CONNECT 时向 Broker 注册遗嘱。当客户端异常断开(网络中断、掉电、未发送 DISCONNECT),Broker 自动代其发布遗嘱消息。Sparkplug 规定遗嘱主题为 spBv1.0/<group_id>/NDEATH/<edge_node_id>,载荷中包含 bdSeq 和时间戳。主应用订阅 NDEATH 后,即可在秒级内感知节点离线,并把该节点下所有设备标记为不可用。

MQTT 发布/订阅模型与 Sparkplug 状态机制 MQTT Broker 主题路由 · QoS 协商 保留消息存储 · 遗嘱代发 Edge Node(PLC01) 发布 NBIRTH / NDATA 注册 NDEATH 遗嘱 QoS 1 · retain 可选 Device(Sensor07) 发布 DBIRTH / DDATA 注册 DDEATH 遗嘱 Primary Host App 订阅 spBv1.0/# 接收 BIRTH 建状态 接收 DEATH 标离线 SCADA / MES 订阅同一主题空间 消费相同状态流 NBIRTH / NDATA DBIRTH / DDATA 分发状态消息 一对多分发 异常断开 → Broker 代发 NDEATH / DDEATH → 上层标记离线 保留消息:新订阅者立即获得 最新 BIRTH 状态快照

五、案例:一条产线的状态建立与掉线感知

假设产线 LineA 上有一台 PLC01,其下挂 Sensor07。系统启动后的典型时序如下:

  1. PLC01 连接 Broker,注册遗嘱主题 spBv1.0/LineA/NDEATH/PLC01,载荷含 bdSeq=0。
  2. PLC01 发布 spBv1.0/LineA/NBIRTH/PLC01,QoS 1,retain=true,列出所有指标与别名。
  3. Sensor07 发布 spBv1.0/LineA/DBIRTH/PLC01/Sensor07,声明自身指标。
  4. 主应用订阅 spBv1.0/LineA/#,立即收到保留的 NBIRTH 与 DBIRTH,建立完整设备树。
  5. 运行期间,PLC01 周期发布 NDATA,Sensor07 发布 DDATA,QoS 1,seq 递增。
  6. 若 PLC01 掉电,Broker 检测到 TCP 断开,代发 NDEATH;主应用收到后把 PLC01 及其下所有设备标记为离线。
状态管理的本质是“事件 + 快照”的组合:BIRTH 提供快照,DATA 提供增量,DEATH 提供终止事件,保留消息保证快照可被迟到者获取,遗嘱消息保证终止事件不依赖设备自身发送。

六、常见误区与复盘

  • 误区一:认为 QoS 2 最安全,全部用 QoS 2。QoS 2 四次握手会显著增加延迟和 Broker 负担,状态消息用 QoS 1 加序号去重更合适。
  • 误区二:忘记设置 retain。NBIRTH 不保留,主应用重启后将长时间处于“未知状态”,直到设备下一次 BIRTH。
  • 误区三:遗嘱主题写错或未注册。遗嘱必须在 CONNECT 报文里声明,连接后再设置无效;主题不符合 Sparkplug 命名空间,主应用无法识别。
  • 误区四:用普通 DISCONNECT 断开。正常断开时 Broker 不会发布遗嘱,若业务上需要“主动下线”语义,应显式发布 NDEATH 或依赖 STATE 主题。
  • 误区五:忽略 bdSeq 与 seq。bdSeq 用于区分同一次会话的 BIRTH/DEATH 配对,seq 用于检测丢包与乱序,二者是状态一致性的关键参数。