消息接入方案分析与决策
在 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",目前的决策是:当前阶段不需要。
为什么当前不引入?
- 避免过度设计 (Over-Engineering):引入 MQ 会显著增加架构复杂度和运维成本
- 性能冗余充足:NestJS (Node.js) 处理 I/O 密集型任务极其高效,单实例处理数千 TPS 无压力
- 共享订阅已满足负载均衡:EMQX 共享订阅在多实例间自动分发消息
何时考虑引入 MQ?
- 业务逻辑涉及复杂的 CPU 密集型计算,阻塞 Event Loop
- 同一份数据需要分发给多个独立的异构系统
- 百万级设备同时上报的极端并发场景
5. 决策总结
| 场景 | 方式 | 说明 |
|---|---|---|
| 高频业务数据(Telemetry/Command) | MQTT Client(TCP 长连接) | 共享订阅,极低延迟 |
| 设备在线状态 | 属性上报驱动 + Redis TTL | 零 EMQX 配置依赖 |
| 设备认证 | EMQX PostgreSQL 直连 | 零后端依赖 |
| 消息队列 | 暂不引入 | 当前规模无需 |
核心理念:EMQX 和后端尽量解耦,各自独立运行。EMQX 负责 MQTT 协议和认证(直连 DB),后端负责业务逻辑(通过 MQTT Client 订阅消息)。两者之间没有 HTTP 回调依赖。
