最近在做一个“近距离、低延迟”的设备互联方案,用的是 MQTT + 本地局域网,主设备和子设备之间要做到“同行”通信。说实话,一开始我也觉得这玩意儿不就是个消息队列吗?但真正落地的时候才发现,这里面的门道比想象中多得多。
这里不写具体项目名,就叫它“客户端 SDK”和“服务端 SDK”,方便对照。今天咱们就像老朋友聊天一样,聊聊为什么要做本地化、MQTT 为啥合适,以及主设备和子设备之间到底是怎么“同行”的。
为什么要本地化?云端的“慢”和“贵”
最开始我们也是走云端,所有消息都通过云服务器转发。但很快就发现两个问题:
第一个是延迟。用户在主设备上点个按钮,指令要经过:主设备 → 路由器 → 互联网 → 云服务器 → 互联网 → 路由器 → 子设备。这一圈下来,即使网络再好,也得 100-200ms。如果网络稍微差一点,500ms 甚至 1 秒都有可能。用户点完按钮,等半秒才看到设备响应,体验就很糟糕。
第二个是成本。如果子设备每秒上报一次状态,100 个子设备就是每秒 100 条消息。一天下来就是 864 万条。每条消息都要走云,带宽成本、服务器成本,算下来不是小数目。
所以我们就想:既然主设备和子设备都在同一个局域网里,为啥不让他们直接“对话”呢?
MQTT 为什么合适?轻量、灵活、可靠
选 MQTT 之前,我们也考虑过其他方案。比如直接用 TCP Socket、WebSocket,或者用 CoAP。但最后选了 MQTT,主要是这几个原因:
轻量。MQTT 协议头很小,一条控制消息可能只有几十字节。对于资源受限的子设备来说,这点很重要。
主题灵活。你可以用gateway/{id}/down这样的主题,也可以根据设备类型、区域做更细的划分。想广播就订阅broadcast/+,想点对点就订阅具体的设备 ID。这种灵活性是其他协议很难做到的。
QoS 可选。心跳用 QoS 0,丢了就丢了;控制指令用 QoS 1,至少送达一次;关键状态用 QoS 2,保证只送达一次。你可以根据消息的重要性选择不同的可靠性级别。
生态成熟。各种语言的客户端库都有,Broker 也很多选择。不管是嵌入式设备还是移动端,都能找到合适的实现。
主/子设备“同行”通信到底是什么意思?
这个词听起来有点抽象,其实说白了就是:主设备和子设备在同一个局域网里,通过本地 Broker 直接通信,不需要经过云端。
举个例子:
场景一:主设备控制多个子设备
用户在主设备上点“全部开灯”,主设备通过本地 Broker 向所有子设备发送控制指令。因为都在同一个局域网,延迟只有几毫秒。用户点完按钮,几乎瞬间就能看到所有灯都亮了。
如果走云端呢?主设备先发到云,云再转发给各个子设备。如果某个子设备网络不好,可能其他灯都亮了,它还在“转圈圈”。
场景二:子设备上报状态,主设备做聚合
比如你有 10 个温度传感器,每个每秒上报一次温度。如果都走云端,云端要处理 10 条消息,然后主设备再去云端拉取。但如果走本地,10 个传感器直接发给本地 Broker,主设备订阅一个主题就能收到所有数据,还能在本地做聚合计算。
场景三:临时组网,没有外网也能用
这个场景很常见。比如你在一个没有外网的仓库里部署了一套设备,如果完全依赖云端,那就用不了。但如果用本地化方案,即使没有外网,主设备和子设备之间也能正常通信。
客户端 SDK 和服务端 SDK 到底在干什么?
这两个 SDK 的名字听起来有点绕,其实它们的职责很清晰:
客户端 SDK跑在主设备或者移动端 App 上。它的主要工作是:
- 连接到本地 Broker
- 订阅需要的主题(比如所有子设备的上报主题)
- 发布控制指令(比如开灯、关灯)
- 管理连接状态(重连、保活)
- 提供 UI 配置入口(让用户能看到连接状态、配置 Broker 地址等)
服务端 SDK跑在本地网关或者边缘节点上。它的主要工作是:
- 作为 Broker 的“代理层”,做主题路由
- 校验权限(比如某个客户端能不能订阅某个主题)
- 消息转发(把主设备的指令转发给对应的子设备)
- 必要时透传到云端(比如本地失败时的兜底)
听起来服务端 SDK 好像更复杂?其实不是。它更像是一个“智能路由器”,知道哪些消息该往哪走。
本地和云端,到底怎么分工?
这是很多人会问的问题:既然做了本地化,还要云端干嘛?
我们的策略是:本地优先,云端兜底。
首选本地。如果主设备和子设备在同一个局域网,优先走本地 Broker。延迟低、体验好。
云端兜底。如果本地 Broker 不可达(比如主设备重启了,或者网络有问题),自动切换到云端。虽然慢一点,但至少能用。
双通道并行。对于特别重要的控制指令,可以本地和云端同时发送,哪个先到用哪个。这样即使本地出问题,云端也能保证指令送达。
数据粒度区分。高频的状态上报(比如温度、湿度)走本地,减少云端压力。关键的控制指令可以双写,保证可靠性。
配置下发。主题前缀、设备 ID、区域码这些配置,还是从云端下发。这样客户端 SDK 不需要硬编码,灵活性更好。
主题设计的基本思路(先埋个伏笔)
主题设计是个大话题,这里先简单说下思路,详细的设计会在系列二展开。
基本思路是:
- 按角色分层:比如
gateway/{id}/down是主设备下行,sub/{id}/up是子设备上行 - 广播和单播并存:群控用广播主题,点控用单播主题
- 避免硬编码:主题前缀、设备 ID、区域码都从配置接口获取,不要写死在代码里
这样设计的好处是灵活。如果以后要支持新的设备类型,或者要分区域部署,只需要改配置,不需要改代码。
常见坑点:你以为很简单,其实…
做本地化 MQTT 的时候,我踩过不少坑。这里先列几个最常见的:
坑一:Keepalive 设置不合理
Keepalive 太短,网络稍微波动就断开;Keepalive 太长,设备真的掉线了也发现不了。我们的经验是:局域网环境 30-60 秒比较合适,移动网络可以适当延长。
坑二:重连没有回退
如果连接失败,不要立即重连。应该用指数回退:第一次等 1 秒,第二次等 2 秒,第三次等 4 秒… 否则很容易造成“重连风暴”,把 Broker 搞崩。
坑三:主题设计太随意
一开始我们主题设计得很随意,后来发现不同设备类型、不同区域的主题混在一起,很难管理。所以后来统一了规范:按角色、按区域、按版本分层。
坑四:没有做消息去重
MQTT QoS 1 保证至少送达一次,但可能会重复。如果你的控制指令是“开灯”,重复收到两次也没问题。但如果是“切换状态”,重复收到就会出问题。所以关键指令要做去重,用请求 ID 或者时间戳。
总结一下
写这篇文章,其实就是想说明白一件事:本地化 MQTT 不是简单的“把云端换成本地”,而是一套完整的架构设计。
- 本地化是为了低延迟和高可用,但云端仍然是重要的兜底和配置来源
- MQTT 的轻量、灵活、可靠,让它很适合做本地化通信
- 主/子设备“同行”,就是在同一局域网内通过本地 Broker 直接通信
- 客户端 SDK 和服务端 SDK 各司其职,一个负责连接和 UI,一个负责路由和转发
- 主题设计要规范,避免硬编码,为后续扩展留空间
如果你正准备落地本地化 MQTT,建议先想清楚这几个问题:
- 你的主设备和子设备是否都在同一局域网?
- 你对延迟的要求有多高?
- 如果本地 Broker 不可达,是否有云端兜底方案?
- 你的主题设计是否考虑了后续扩展?
想清楚这些问题,再进入后续的架构设计和代码实现,会少走很多弯路。
下一篇(系列二)我们会深入聊架构设计、主题命名规范、QoS 选择、会话管理这些细节。如果你对某个点特别感兴趣,也可以先看对应的章节。