Skip to content

消息接入方案分析与决策

在 YunStack-IoT 项目中,后端服务如何从 EMQX 获取设备数据是一个关键架构决策。主要有两种方式:**后端作为 MQTT Client(TCP 长连接)**和 设备状态通过属性上报驱动

1. 高频业务数据:后端作为 MQTT Client

工作原理

后端服务启动一个 MQTT 客户端,通过 TCP 长连接订阅通配符 Topic(如 $share/group1/sys/+/+/thing/#)。

优势

  • 极低延迟:TCP 长连接,微秒/毫秒级即时性
  • 高吞吐:Node.js 处理 TCP 流极其高效,适合高频小包
  • 低协议开销:相比 HTTP,MQTT 几乎没有额外协议头开销
  • 水平扩展:通过共享订阅($share/group1/)实现多实例负载均衡

为什么不使用 Webhook 传输业务数据?

  • 高并发压力:假设系统有 1000 个设备每秒上报一次数据,使用 Webhook 意味着后端每秒需处理 1000 个 HTTP 请求。HTTP Header 往往比 MQTT Payload 还大,带宽利用率低
  • 协议开销大:每条消息都需要 HTTP 握手、传输、断开,MQTT 长连接无此开销

2. 设备在线状态:属性上报驱动 + Redis TTL

方案

在线状态不使用 Webhook,而是由设备属性上报消息驱动:

设备上报属性 → 后端在 Redis 中 SET device:online:{id} = 1, EX 120s → TTL 过期自动离线

为什么不使用 Webhook 做上下线感知?

  • 部署耦合:Webhook URL 需要配置 http://backend:3000/...,每个环境(测试/生产)的地址不同,切换环境就要改 EMQX 配置。对于追求解耦的部署方案,这是不必要的耦合点
  • 语义更准:"在线" = "最近 120 秒内有数据上报",这比 "TCP 连接着但设备可能已死机" 更有业务意义
  • 简洁可靠:不依赖 EMQX 的回调机制,后端和 EMQX 各自独立运行,后端代码完全控制在线判定逻辑
  • 部署零配置:EMQX 只需配置数据库连接和 ACL,无需关心后端地址

兜底考量

120 秒 TTL 内无属性上报即判定离线,对于大部分 IoT 场景(传感器周期性上报、继电器状态变化上报)已足够准确。

3. 设备认证:PostgreSQL 直连

方案

设备 MQTT 连接时,EMQX 直接查询 PostgreSQL devices 表进行认证,不走 HTTP 回调:

设备 MQTT CONNECT → EMQX 查 PostgreSQL → 校验凭证 → allow/deny

优势

  • 性能更好:少一层 HTTP 往返,认证延迟更低
  • 链路独立:MQTT 认证不依赖后端存活,后端重启不影响设备连接和上报
  • 配置一致:测试和生产环境共用同一套 EMQX 认证配置(都连同一个数据库)

设备需提前创建在数据库中(通过配网流程或管理后台预录入),连接时不会自动注册。

4. 补充讨论:是否需要引入消息队列 (MQ)?

关于"是否需要在 EMQX 和 NestJS 之间引入 Kafka/RabbitMQ",目前的决策是:当前阶段不需要

为什么当前不引入?

  1. 避免过度设计 (Over-Engineering):引入 MQ 会显著增加架构复杂度和运维成本
  2. 性能冗余充足:NestJS (Node.js) 处理 I/O 密集型任务极其高效,单实例处理数千 TPS 无压力
  3. 共享订阅已满足负载均衡:EMQX 共享订阅在多实例间自动分发消息

何时考虑引入 MQ?

  • 业务逻辑涉及复杂的 CPU 密集型计算,阻塞 Event Loop
  • 同一份数据需要分发给多个独立的异构系统
  • 百万级设备同时上报的极端并发场景

5. 决策总结

场景方式说明
高频业务数据(Telemetry/Command)MQTT Client(TCP 长连接)共享订阅,极低延迟
设备在线状态属性上报驱动 + Redis TTL零 EMQX 配置依赖
设备认证EMQX PostgreSQL 直连零后端依赖
消息队列暂不引入当前规模无需

核心理念:EMQX 和后端尽量解耦,各自独立运行。EMQX 负责 MQTT 协议和认证(直连 DB),后端负责业务逻辑(通过 MQTT Client 订阅消息)。两者之间没有 HTTP 回调依赖。