3、通配符使用场景:订阅多个主题、主题过滤、实际应用案例
在理解了 + 与 # 通配符的语法规则之后,真正关键的问题浮出水面:我们究竟应该在哪些场景中使用它们? 如果只是机械地记住“+ 匹配一层,# 匹配多层”,遇到实际业务时依然会手足无措。本节将围绕三个最典型的应用场景展开:批量订阅多个主题、按层级进行主题过滤,以及一个完整的物联网温度监控案例。通过这三个场景,你会理解通配符不是“偷懒的简写”,而是 MQTT 主题系统设计的核心表达方式。
3.1 场景一:批量订阅多个主题——从“点对点”到“面”
假设你正在开发一个智能楼宇的能耗监控系统,楼内每层安装了 20 个电表,主题设计为:
building/floor1/meter01/power
building/floor1/meter02/power
...
building/floor1/meter20/power
如果不使用通配符,你需要写 20 条订阅语句。如果楼有 10 层,那就是 200 条。这显然不可维护。此时,+ 通配符可以精准地解决“某一层所有电表”的订阅需求:
// 订阅 1 层所有电表的功率数据
client.subscribe("building/floor1/+/power")
// 订阅所有楼层所有电表的功率数据
client.subscribe("building/+/+/power")
注意,building/+/+/power 中的两个 + 分别对应“楼层号”和“电表编号”两个层级。这种写法将订阅范围从“一个点”扩展到了“一个面”,但依然保持了主题的结构化边界。下图展示了这种“批量订阅”与“精确订阅”的差异:
这里有一个容易被忽略的细节:通配符订阅是“订阅时过滤”,而不是“发布时过滤”。也就是说,无论客户端是否订阅,发布者都可以向任意主题发布消息;只有当消息到达 Broker 时,Broker 才会根据订阅关系中的通配符进行匹配并转发。理解这一点,有助于后续排查“为什么我订阅了却没收到消息”的问题。
3.2 场景二:主题过滤——在 Broker 端实现“按需分发”
通配符的第二个核心价值是主题过滤。在物联网系统中,一个传感器节点往往会上报多种类型的数据,例如温湿度、电量、信号强度等。如果所有数据都发往同一个主题,接收方将难以区分;如果每个数据都单独建一个主题,又会导致主题数量爆炸。通配符提供了一种优雅的折中方案:用主题层级表达数据分类,用通配符表达订阅意图。
以农业大棚监测为例,每个大棚节点会上报如下主题:
farm/greenhouse01/temperature
farm/greenhouse01/humidity
farm/greenhouse01/soil_moisture
farm/greenhouse01/battery
不同的下游系统需要不同的数据子集:
- 环境监控大屏:只需要温度与湿度,订阅
farm/greenhouse01/+会收到包括土壤湿度和电量在内的全部数据,造成带宽浪费。更优做法是订阅farm/greenhouse01/temperature和farm/greenhouse01/humidity两条精确主题,或者重新设计主题层级,将环境数据与设备数据分离。 - 设备运维系统:只关心电池电量,订阅
farm/+/battery即可获取所有大棚的电量状态。 - 总控平台:需要全部数据,直接订阅
farm/#。
下表对比了不同订阅方式在“数据接收范围”上的差异,帮助你建立直观印象:
| 订阅表达式 | 匹配的主题示例 | 不匹配的主题示例 | 适用场景 |
|---|---|---|---|
farm/greenhouse01/# |
temperature、humidity、battery | farm/greenhouse02/temperature | 单棚全量数据 |
farm/+/battery |
greenhouse01/battery、greenhouse02/battery | greenhouse01/temperature | 跨棚同类型数据 |
farm/greenhouse01/+ |
temperature、humidity、soil_moisture | farm/greenhouse01/battery/voltage(多一层) | 单棚非递归数据 |
farm/# |
所有以 farm/ 开头的主题 | factory/line1/temperature | 总控全量汇聚 |
从上表可以看出,+ 与 # 的搭配使用,本质上是在“数据广度”与“层级深度”两个维度上做裁剪。实际项目中,建议先画出主题树,再根据每个订阅方的需求,用通配符在树上“圈出”对应的子树。
3.3 场景三:实际应用案例——冷链运输温度监控
下面通过一个完整的案例,将通配符的使用串联起来。假设你为一辆冷链运输车设计温度监控系统,每辆车上有 4 个温度传感器,分别位于车厢前、后、左、右。主题设计如下:
coldchain/truck{id}/sensor{position}/temperature
其中 {id} 为车牌号后四位,{position} 为 front、rear、left、right。例如:
coldchain/truck1024/sensorfront/temperature
coldchain/truck1024/sensorrear/temperature
现在有三个不同的接收端程序需要接入:
- 车载本地告警器:需要监控本车 4 个传感器的温度,一旦超过阈值立即声光报警。订阅:
coldchain/truck1024/+/temperature。这里使用+匹配任意传感器位置,但限定在 truck1024 这一辆车内。 - 物流中心监控大屏:需要查看所有在途车辆的所有传感器数据。订阅:
coldchain/#。这是最宽泛的订阅,能收到所有车辆、所有位置的数据。 - 数据分析平台:只关心每辆车的平均温度,因此只需要每辆车
front位置的数据作为代表。订阅:coldchain/+/sensorfront/temperature。这里用+匹配所有车辆 ID,但固定了传感器位置。
下面用一张图来展示这个案例中,三个订阅者如何通过不同的通配符表达式,从同一棵主题树中获取各自关心的数据子集:
这个案例揭示了通配符使用的三个关键经验:
- 通配符的位置决定了过滤的维度。在车辆 ID 层级使用
+,意味着“横向跨车辆”;在传感器位置层级使用+,意味着“纵向跨传感器”。设计订阅时,先明确你要在哪个维度上聚合数据。 - 不要滥用
#。监控大屏订阅coldchain/#是合理的,因为它确实需要全量数据。但如果数据分析平台也订阅#,它就会收到大量无关的sensorrear、sensorleft数据,徒增网络与计算开销。 - 通配符与主题设计强相关。如果当初把主题设计成
coldchain/truck1024_temperature_front(用下划线拼接),那么通配符将完全失效。因此,主题的层级化设计是通配符发挥作用的前提。
3.4 常见误区与复盘
在实际项目中,围绕通配符使用经常出现以下几类问题,值得在编码前就加以规避:
误区一:把 + 当作“任意字符串”使用
例如,有人想订阅所有以 sensor 开头的主题,写成 coldchain/truck1024/sensor+/temperature。这是非法的,因为 + 只能占据整个层级,不能与普通字符混用。正确做法是:要么将传感器类型也设计为独立层级(如 coldchain/truck1024/sensor_type/temperature),要么使用精确主题逐一订阅。
误区二:混淆 + 与 # 的匹配深度
订阅 coldchain/truck1024/+ 只能匹配 coldchain/truck1024/sensorfront 这一层,无法匹配 coldchain/truck1024/sensorfront/temperature(因为后者多了一层)。如果需要匹配任意深度,必须使用 #。一个简单的记忆口诀:+ 管一层,# 管到底。
复盘建议:在编写订阅代码前,先用纸笔画出主题树,并针对每个订阅方标注其需要的数据子树。然后检查:
- 该订阅表达式是否精确覆盖了目标子树?
- 是否意外覆盖了不需要的兄弟子树?
- 如果未来新增设备或传感器,该表达式是否需要修改?
通过这三步检查,可以避免绝大多数通配符误用问题。
总结而言,通配符的价值在于用一条订阅表达一类数据需求。它要求设计者具备“主题树思维”——将数据源按层级组织,再让订阅方按需裁剪。掌握 + 与 # 的适用场景,是 MQTT 应用从“能通”走向“会设计”的关键一步。