嵌入式开发 · 传感器应用 · 物联网通信 · 学习与作品记录

← 返回笔记列表

MQTT 主题表设计:为什么设备只能发自己的、只能收自己的 ack

设备接入 MQTT 之后,第一个要定的是主题怎么分层、账号怎么分权限。这两个决定一旦定下,固件、后端、Broker 配置都要跟着走,改起来代价很大。

核心约束

系统里只有两种身份连 Broker:

  • 设备(ESP32-S3):只发自己的事件与状态,只收自己的 ack;
  • 后端:订阅所有设备的事件与状态,向任意设备下发 ack。

家属端不是 Broker 客户端——它只与后端交互,不持有任何 Broker 凭据。

MQTT 主题与 ACL 权限:设备发布 elder/bath01/# 并订阅 elder/bath01/ack;后端订阅 elder/+/event/# 与 elder/+/state,发布 elder/+/ack

为什么给设备开 elder/bath01/# 的读权限是错的

看起来「设备只能碰自己房间的主题」已经很安全了,但读权限开成通配会带来两个具体问题:

  1. state 回环:设备自己发布的 elder/bath01/state 会被自己收到,白白占用带宽和解析开销;
  2. 泄露面放大:一旦设备账号泄露,攻击者可以读到该设备全部上行内容,而不是只拿到 ack 通道。

所以设备的订阅范围应该收窄到一条:只订阅 elder/<device_id>/ack。

ACL 的实际写法

# 设备账号:只写自己的全部主题,只读自己的 ack
user device-bath01
topic write elder/bath01/#
topic read  elder/bath01/ack

# 后端账号:读所有设备的事件与状态,写 ack
user backend-svc
topic read  elder/+/event/#
topic read  elder/+/state
topic write elder/+/ack

Mosquitto 里 read 是订阅(收),write 是发布(发)。写反了会出现「设备能发但收不到 ack」这种极难查的故障。

三个连接层参数

参数取值理由
QoS1至少一次,配合 event_id 去重,比 QoS 2 省一半握手
Clean Sessiontrue(两端都是)设备侧已自己做断网补传,Broker 再补投会制造第二条路径与重复事件
Keep Alive60 s超时 1.5 倍即判定掉线(90 s),与 LWT 配合做在线状态

连接走 8883/TLS;端口开放、证书与备案期间的过渡方案见部署形态定案。

一个容易忽略的点

Clean Session 全程 true 意味着 Broker 不保存会话。这样一来,设备离线期间的事件只能靠设备自己缓存——这正是 ESP32-S3 上 LittleFS 存在的理由。两条补传路径同时存在,只会带来重复投递。