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 中的两个 + 分别对应“楼层号”和“电表编号”两个层级。这种写法将订阅范围从“一个点”扩展到了“一个面”,但依然保持了主题的结构化边界。下图展示了这种“批量订阅”与“精确订阅”的差异:

精确订阅(无通配符) 客户端 floor1/meter01/power floor1/meter02/power floor1/meter03/power N 条订阅语句 维护困难,易遗漏 通配符订阅 客户端 floor1/+/power 匹配 meter01~meter20 以及未来新增的电表 1 条订阅语句 动态适配,自动覆盖 + 匹配 任意一层 核心差异 精确订阅 = 静态绑定 通配符订阅 = 动态过滤 新增设备无需改代码

这里有一个容易被忽略的细节:通配符订阅是“订阅时过滤”,而不是“发布时过滤”。也就是说,无论客户端是否订阅,发布者都可以向任意主题发布消息;只有当消息到达 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

现在有三个不同的接收端程序需要接入:

  1. 车载本地告警器:需要监控本车 4 个传感器的温度,一旦超过阈值立即声光报警。订阅:coldchain/truck1024/+/temperature。这里使用 + 匹配任意传感器位置,但限定在 truck1024 这一辆车内。
  2. 物流中心监控大屏:需要查看所有在途车辆的所有传感器数据。订阅:coldchain/#。这是最宽泛的订阅,能收到所有车辆、所有位置的数据。
  3. 数据分析平台:只关心每辆车的平均温度,因此只需要每辆车 front 位置的数据作为代表。订阅:coldchain/+/sensorfront/temperature。这里用 + 匹配所有车辆 ID,但固定了传感器位置。

下面用一张图来展示这个案例中,三个订阅者如何通过不同的通配符表达式,从同一棵主题树中获取各自关心的数据子集:

冷链运输温度监控——通配符订阅案例 主题树(部分) coldchain truck1024 truck2048 sensorfront sensorrear temperature temperature sensorfront sensorrear temperature temperature 三个订阅者 ① 车载告警器 coldchain/truck1024/+/temperature 匹配:本车4个传感器温度 ② 监控大屏 coldchain/# 匹配:所有车辆所有传感器 ③ 数据分析平台 coldchain/+/sensorfront/temperature 匹配:每辆车front位置温度 + # +

这个案例揭示了通配符使用的三个关键经验:

  • 通配符的位置决定了过滤的维度。在车辆 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(因为后者多了一层)。如果需要匹配任意深度,必须使用 #。一个简单的记忆口诀:+ 管一层,# 管到底。

复盘建议:在编写订阅代码前,先用纸笔画出主题树,并针对每个订阅方标注其需要的数据子树。然后检查:

  1. 该订阅表达式是否精确覆盖了目标子树?
  2. 是否意外覆盖了不需要的兄弟子树?
  3. 如果未来新增设备或传感器,该表达式是否需要修改?

通过这三步检查,可以避免绝大多数通配符误用问题。

总结而言,通配符的价值在于用一条订阅表达一类数据需求。它要求设计者具备“主题树思维”——将数据源按层级组织,再让订阅方按需裁剪。掌握 + 与 # 的适用场景,是 MQTT 应用从“能通”走向“会设计”的关键一步。