设备在线不用浏览器长连接:MQTT 心跳 + Redis TTL 的在线状态设计
设备在线看起来简单:连着就在线,断了就离线。但在无人机、机库、传感器这类平台里,若让浏览器直连 Broker,或靠前端长连接判断在线,安全和一致性都难扛。
更稳妥的做法是:设备经 MQTT 上报心跳,后端消费后写 Redis TTL,前端仍用普通 HTTP 查设备列表里的在线状态。
下文是一套脱敏后的在线设计,不暴露真实 MQTT 账号、Topic 和设备编号。
一、为什么浏览器不直接连 Broker
浏览器直连 MQTT Broker 看似省事,问题不少:
| 问题 | 说明 |
|---|---|
| 凭证暴露 | Broker 用户名/密码容易被抓到 |
| 权限粗 | 前端很难精细限制 Topic |
| 数据不一致 | 每个浏览器各自判断在线,结果可能不同 |
| 难审计 | 谁订阅了什么 Topic 不好追踪 |
所以更推荐:设备接入归设备服务处理,浏览器只看业务 API。
二、整体架构
flowchart LR
D[设备/模拟器] -->|heartbeat| B[MQTT Broker]
B --> J[Java/Node 设备接入服务]
J --> R[(Redis TTL)]
J --> DB[(设备元数据)]
FE[前端] --> API[HTTP 设备列表 API]
API --> DB
API --> R
API --> FE
读图: 在线事实落在服务端:心跳进 Broker,接入服务刷新 Redis TTL;浏览器只调 HTTP,从不碰设备 Broker。
设备在线不需要浏览器维持一条设备长连接。前端定期请求设备列表,服务端把 Redis TTL 状态拼进去即可。
三、心跳 Topic 与 Payload
对外文章里不要贴真实 Topic,可抽象成:
1 | devices/{deviceId}/heartbeat |
Payload 示例:
1 | { |
后端消费后只做几件事:
- 校验 deviceId 是否存在
- 校验时间戳是否合理
- 写 Redis 在线 key,设置 TTL
- 可选:更新最新电量/状态缓存
四、Redis TTL 在线判定
sequenceDiagram
participant D as 设备
participant M as MQTT Broker
participant S as 设备服务
participant R as Redis
participant FE as 前端
D->>M: heartbeat
M->>S: 订阅消费
S->>R: SET presence:DRONE-001 online EX 90
FE->>S: GET /devices
S->>R: EXISTS presence:DRONE-001
R-->>S: 1
S-->>FE: online=true
假设设备每 30 秒发一次心跳,TTL 可设成 90 秒:
1 | const ttlSeconds = 90; |
设备断电或网络断开时,不必额外发离线消息,TTL 到期自然离线。
五、状态流转
stateDiagram-v2
[*] --> 未知
未知 --> 在线: 收到心跳
在线 --> 在线: 持续收到心跳 刷新TTL
在线 --> 离线: TTL过期
离线 --> 在线: 再次收到心跳
这套状态机的好处是:离线由时间自然推导,不依赖设备主动发「我下线了」。
六、前端为什么仍用 HTTP
前端展示设备列表时可以继续用 HTTP:
1 | async function fetchDevices() { |
优点:
- 页面刷新不丢状态
- 列表分页、筛选、权限都走同一套 API
- 不必把 MQTT 凭证暴露给浏览器
- 服务端可统一做租户/项目权限过滤
若 UI 要更实时,可每 10–30 秒轮询一次,或由 WebSocket 只推「状态变化事件」,仍不让浏览器直连设备 Broker。
七、边界情况
| 场景 | 处理 |
|---|---|
| 心跳延迟 | TTL 不要设太短,至少 2–3 倍心跳间隔 |
| 设备时间不准 | 在线判定以服务端消费时间为准 |
| 多实例消费 | MQTT 接入服务要避免重复消费 |
| Redis 重启 | 在线状态可短暂未知,由下一次心跳恢复 |
| 设备主动下线 | 可立即 DEL presence key,但不能只依赖它 |
八、与实时遥测的关系
在线状态和遥测流不要混成一个概念:
flowchart TB
A[设备消息] --> B{消息类型}
B -->|heartbeat| C[刷新 Redis TTL]
B -->|telemetry| D[更新最新位置/轨迹]
B -->|alarm| E[进入告警链路]
C --> F[设备列表 online]
D --> G[地图实时位置]
E --> H[告警中心]
心跳只回答「设备是否活着」;遥测回答「设备在哪里、状态如何」。两条链路可以共享 Broker,业务含义不同。
九、小结
设备在线的关键,不是让前端连上 MQTT,而是建立可靠的服务端事实源:
- 设备发心跳
- 后端消费并刷新 Redis TTL
- TTL 过期自然离线
- 前端通过 HTTP 查询聚合后的在线状态
这套设计简单、可观测、好运维,也能避免把设备接入层的复杂度暴露给浏览器。