- 物联网实战|Spring Boot + Netty 搭建 MQTT 消息路由与流转层
- sample-00-iot-routing 物联网消息路由模块
物联网实战|Spring Boot + Netty 搭建 MQTT 消息路由与流转层
源码(sample-00-iot-routing)
sample-00-iot-routing 物联网消息路由模块
模块概述
sample-00-iot-routing 是物联网平台的核心消息路由模块,负责维护设备订阅关系与消息分发。该模块处于接入层与业务层之间,接收来自接入层的上行消息事件,根据订阅关系匹配目标设备,通过 Netty Channel 将消息转发给在线订阅者。
模块职责
维护主题订阅关系,支持 MQTT 通配符 + 与 # 规则匹配 管理订阅关系存储,提供内存与 Redis 双模式实现 处理上行消息分发,根据主题查找订阅者并转发消息 处理下行消息发送,根据协议类型封装不同格式消息 记录订阅操作日志,支持订阅行为审计
模块结构
消息事件源(接入层)
│
▼
DeviceSubscribeEventListener ───► SubscriptionManager
│ ├── 内存模式 (Inner)
│ └── Redis模式 (Redis + Caffeine)
│
▼
TopicMatcher (Trie树实现)
│
▼
MessageEventListener(异步处理)
│
├── 主题匹配查找订阅者
│
▼
MessageSendDispatcher
│
├── MQTT 协议格式封装
│
└── TCP 协议格式封装
│
▼
RegManager (获取设备 Channel)
│
▼
Netty Channel.writeAndFlush() ───► 目标设备
目录结构
sample-00-iot-routing/
├── src/main/java/com/jysemel/iot/routing/
│ ├── config/
│ │ └── RoutingTraceIdTaskExecutorAsyncConfig.java # 异步线程池配置,传递 TraceId
│ ├── dispatcher/
│ │ └── MessageSendDispatcher.java # 下行消息分发器,支持多协议发送
│ ├── listener/
│ │ ├── DeviceSubscribeEventListener.java # 设备订阅事件监听器
│ │ └── MessageEventListener.java # 消息事件监听器,处理消息分发
│ ├── matcher/
│ │ ├── TopicMatcher.java # 主题匹配器接口
│ │ └── TrieTopicMatcher.java # Trie树实现的主题匹配器
│ ├── subscription/
│ │ ├── Subscription.java # 订阅实体
│ │ ├── SubscriptionManager.java # 订阅管理器接口
│ │ └── impl/
│ │ ├── InnerMemerySubscriptionManager.java # 内存模式订阅管理
│ │ └── RedisSubscriptionManager.java # Redis模式订阅管理
│ └── subscriptionlog/
│ ├── SubscriptionLogDO.java # 订阅日志实体
│ ├── SubscriptionLogRepository.java # 订阅日志仓储接口
│ ├── SubscriptionLogService.java # 订阅日志服务接口
│ └── impl/
│ ├── MySqlSubscriptionLogService.java # MySQL订阅日志实现
│ └── SqliteSubscriptionLogService.java # SQLite订阅日志实现
├── README.md
└── pom.xml
关键实体
Subscription 订阅实体
表示一条订阅关系,包含以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| clientId | String | 客户端唯一标识 |
| topic | String | 订阅主题,可包含通配符 |
| qos | int | 服务质量等级 (0, 1, 2) |
| subscribeTime | long | 订阅时间戳,毫秒级 |
| deviceId | String | 设备ID,可选字段 |
SubscriptionLogDO 订阅日志实体
记录订阅操作的审计日志,包含以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Long | 主键,自增 |
| clientId | String | 客户端标识,长度限制 128 |
| topic | String | 订阅主题,长度限制 256 |
| qos | Integer | 服务质量等级 |
| action | String | 操作类型,可选 SUBSCRIBE / UNSUBSCRIBE / UNSUBSCRIBE_ALL |
| protocol | String | 协议类型,如 MQTT / TCP |
| actionTime | LocalDateTime | 操作时间 |
| recordTime | LocalDateTime | 记录时间,自动生成 |
| remark | String | 备注信息,长度限制 255 |
主题匹配机制
TopicMatcher 接口
主题匹配器提供核心能力:主题过滤器与发布主题的匹配、按主题查找订阅者、管理订阅关系。
提供的主要方法:
- match(String topicFilter, String topic) - 判断订阅过滤器与主题是否匹配
- findMatches(String topic) - 查找匹配指定主题的所有订阅过滤器
- addSubscription(String topicFilter, String subscriber) - 添加订阅关系
- removeSubscription(String topicFilter, String subscriber) - 移除订阅关系
- clear() - 清空所有订阅关系
TrieTopicMatcher Trie树实现
基于前缀树(Trie树)实现的主题匹配器,支持 MQTT 标准通配符规则。
通配符规则说明:
-
单级通配符
+:匹配主题中的一个层级sensor/+/temperature可匹配sensor/device1/temperature- 不可匹配
sensor/device1/room/temperature
-
多级通配符
#:匹配主题中的剩余所有层级sensor/#可匹配sensor/device1/temperature- 可匹配
sensor/device1/room/temperature/humidity - 必须位于过滤器末尾
内部数据结构:
使用 TrieNode 节点构建主题层级树,每个节点维护子节点映射与订阅者列表。
TrieNode
├── children: Map<String, TrieNode>
│ ├── "sensor" → TrieNode
│ ├── "+" → TrieNode (单级通配符)
│ └── "#" → TrieNode (多级通配符)
└── subscribers: List<String> (订阅过滤器列表)
匹配流程:
消息主题 sensor/device1/temperature 查找订阅流程:
- 按
/分割主题层级:["sensor", "device1", "temperature"] - 从根节点开始,逐级精确匹配
- 在每个层级同时尝试匹配
+通配符节点 - 遇到
#通配符节点,直接返回其订阅者列表 - 收集所有命中的订阅者返回
订阅管理
SubscriptionManager 接口
订阅管理核心接口,定义订阅生命周期管理方法:
- subscribe(String clientId, String topic, int qos) - 添加订阅
- unsubscribe(String clientId, String topic) - 移除指定主题订阅
- unsubscribeAll(String clientId) - 移除客户端所有订阅
- getSubscriptions(String clientId) - 获取客户端所有订阅
- getSubscribers(String topic) - 获取主题的所有订阅者
- findMatchingSubscriptions(String topic) - 查找匹配主题的所有订阅(含通配符)
- isSubscribed(String clientId, String topic) - 检查是否已订阅
InnerMemerySubscriptionManager 内存实现
基于 ConcurrentHashMap 和 CopyOnWriteArrayList 实现的本地内存订阅管理器,适用于单机部署场景。
启用条件:
mqtt.sub=inner
或不配置 mqtt.sub 参数(默认启用)。
数据存储结构:
- clientSubscriptions:
Map<String, List<Subscription>>- Key: clientId
- Value: 客户端订阅列表
- topicMatcher:
TopicMatcher实例,维护主题过滤器索引
主要流程:
subscribe 方法流程:
- 检查客户端是否已订阅相同主题(去重)
- 如未订阅,创建 Subscription 对象并添加到客户端列表
- 将主题过滤器添加到 TopicMatcher 索引中
- 记录日志
unsubscribe 方法流程:
- 从客户端订阅列表中移除指定主题
- 从 TopicMatcher 索引中移除订阅关系
- 记录日志
findMatchingSubscriptions 方法流程:
- 调用 TopicMatcher.findMatches() 获取匹配的主题过滤器
- 对每个过滤器,获取对应的订阅者列表
- 聚合返回结果
特点:
- 高性能,纯内存操作,响应快
- 无需外部依赖(Redis)
- 单机模式下数据不持久化,重启后丢失
- 适合开发测试环境或单点部署
RedisSubscriptionManager Redis 实现
基于 Redis 与 Caffeine 本地缓存的分布式订阅管理器,适用于多节点集群部署场景。
启用条件:
mqtt.sub=redis
数据存储结构:
Redis 数据结构:
-
设备订阅列表 (List): key =
iot:subscription:device:{clientId}- 每个元素为 JSON 序列化的 Subscription 对象
- 过期时间 30 天
-
主题索引 (Set): key =
iot:subscription:topic:{topic}- 每个元素为订阅该主题的 clientId
- 用于快速查找主题订阅者
Caffeine 本地缓存:
-
localCache:
Cache<String, List<Subscription>>- Key: clientId
- Value: 客户端订阅列表
- 最大 10000 条,写入后 10 分钟过期
-
topicCache:
Cache<String, List<Subscription>>- Key: topic
- Value: 主题订阅者列表
- 最大 10000 条,写入后 10 分钟过期
主要流程:
subscribe 方法流程:
- 检查是否已订阅(从缓存或 Redis)
- 未订阅则序列化 Subscription 对象并存入 Redis List
- 将 clientId 添加到主题索引 Set
- 清除本地缓存(使缓存失效)
- 将主题过滤器添加到 TopicMatcher 索引
- 记录日志
getSubscriptions 方法流程(缓存优先):
- 先从 localCache 读取
- 缓存命中则直接返回
- 未命中则从 Redis 读取 List 并反序列化
- 更新本地缓存后返回
getSubscribers 方法流程(缓存优先):
- 先从 topicCache 读取
- 缓存命中则直接返回
- 未命中则从 Redis 主题索引 Set 获取 clientId 列表
- 对每个 clientId,获取其订阅并过滤当前主题
- 聚合后更新本地缓存并返回
特点:
- 支持多节点分布式部署
- 数据持久化在 Redis,重启不丢失
- 通过 Caffeine 本地缓存降低 Redis 压力
- 写入后主动清除缓存,保证数据一致性
- 适合生产环境集群部署
消息分发
MessageEventListener 消息事件监听器
监听 Spring 事件总线的 MessageEvent 事件,在异步线程池中处理消息分发。
主要依赖:
- subscriptionManager - 订阅管理器,用于查找订阅者
- messageSendDispatcher - 消息发送分发器,实际发送消息
- eventPublisher - 事件发布器,发布规则引擎事件
事件处理流程:
handleMessageUpstream 方法流程:
- 监听 Spring
@EventListener注解捕获 MessageEvent - 通过
@Async("routingTraceIdTaskExecutor")切换到异步线程池执行 - 从事件中提取 InternalMessage
- 检查消息主题,如果为空则自动生成默认主题
/mock/{clientId}/data - 调用 dispatch 方法执行分发逻辑
dispatch 方法流程:
- 提取消息主题
- 调用
subscriptionManager.findMatchingSubscriptions(topic)查找匹配的订阅 - 无订阅者则直接返回,记录日志
- 有订阅者则遍历每个订阅,创建
MessageDispatchEvent - 调用 publishEvent 执行实际发送
publishEvent 方法流程:
- 调用
messageSendDispatcher.send(targetClientId, originalMessage)发送消息 - 捕获
DeviceOfflineException,记录 WARN 日志(设备离线为正常场景) - 捕获通用 Exception,记录 ERROR 日志
- 发布
RuleEvent事件给规则引擎后续处理 - 捕获规则引擎处理异常并记录日志
日志级别策略:
- 设备离线使用 WARN 级别,属于业务正常情况
- 发送异常使用 ERROR 级别,属于系统异常情况
MessageSendDispatcher 消息发送分发器
下行消息发送核心组件,根据协议类型将 InternalMessage 封装为对应协议格式后发送到目标设备。
主要依赖:
- regManager - 设备注册管理器,用于根据 clientId 查找 Netty Channel
send 方法主流程:
- 通过
regManager.getConnection(clientId)获取设备 Channel - Channel 为空说明设备离线,抛出
DeviceOfflineException - Channel 不为空但
isActive()为 false,说明连接已断开- 清理无效连接记录
- 抛出
DeviceOfflineException
- 读取协议类型,默认为 TCP
- 根据协议类型分发到具体发送方法
- 捕获发送异常,关闭异常连接并清理记录
- 抛出 RuntimeException 供上层处理
sendTcpMessage TCP 发送流程:
- 创建 ByteBuf,预留 payload 长度 + 1 字节
- 写入 payload 字节数组
- 写入换行符
\n作为消息结束标识 - 调用
channel.writeAndFlush(byteBuf)发送 - 记录发送日志,包含完整报文信息
sendMqttPublish MQTT 发送流程:
-
构造 MqttFixedHeader
- 消息类型:PUBLISH
- QoS 等级:从 message.getQos() 获取
- Retain 标志:从 message.isRetained() 获取
- RemainingLength: 0 (Netty 自动计算)
-
构造 MqttPublishVariableHeader
- Topic: 消息主题
- PacketId: QoS > 0 时生成 (System.currentTimeMillis() % 65535)
- PacketId: QoS = 0 时为 0
-
构造消息体 ByteBuf
- 创建 payload 长度的缓冲区
- 写入 payload 字节数组
-
组装 MqttPublishMessage 并通过 Netty Channel 发送
-
记录发送日志
异常处理策略:
- 发送过程中发生任何异常,记录 ERROR 日志
- 如果 Channel 仍处于 active 状态,主动关闭
- 清理 RegManager 中的连接记录
- 抛出 RuntimeException 供上层业务处理
订阅事件处理
DeviceSubscribeEventListener 设备订阅事件监听器
监听设备订阅/取消订阅事件,维护订阅关系并记录操作日志。
事件源:接入层通过 Spring 事件总线发布 DeviceSubscribeEvent
主要依赖:
- subscriptionManager - 订阅管理器,可选注入(required=false)
- subscriptionLogService - 订阅日志服务,可选注入(required=false)
handleDeviceSubscribe 方法流程:
-
检查 subscriptionManager 是否注入,未注入则记录 WARN 日志并跳过
-
获取事件信息:clientId、事件类型、协议类型(默认 MQTT)
-
根据事件类型 switch 处理:
- SUBSCRIBE 类型:调用
subscriptionManager.subscribe()添加订阅,记录操作日志 - UNSUBSCRIBE 类型:调用
subscriptionManager.unsubscribe()移除订阅,记录操作日志 - UNSUBSCRIBE_ALL 类型:调用
subscriptionManager.unsubscribeAll()移除全部订阅,记录操作日志
- SUBSCRIBE 类型:调用
-
捕获通用异常,记录 ERROR 日志
异步处理:通过 @Async("routingTraceIdTaskExecutor") 在独立线程池中执行,避免阻塞主线程。
订阅日志服务
SubscriptionLogService 接口
定义订阅操作日志记录能力:
- logSubscribe(String clientId, String topic, Integer qos, String protocol) - 记录订阅操作
- logUnsubscribe(String clientId, String topic, String protocol) - 记录取消订阅操作
- logUnsubscribeAll(String clientId, String protocol) - 记录取消全部订阅操作
异步线程池配置
RoutingTraceIdTaskExecutorAsyncConfig
配置路由模块专用异步线程池,支持在异步线程中传递 TraceId 以实现完整链路追踪。
线程池参数:
| 参数 | 值 | 说明 |
|---|---|---|
| corePoolSize | 4 | 核心线程数 |
| maxPoolSize | 8 | 最大线程数 |
| queueCapacity | 500 | 任务队列容量 |
| threadNamePrefix | "async-multi-" | 线程名前缀 |
TraceId 传递机制:
通过 TaskDecorator 在切换线程时复制 MDC (Mapped Diagnostic Context) 上下文:
- 在提交任务前,捕获当前线程的 MDC 中指定 key 的值
- 在执行任务时,将捕获的值设置到新线程的 MDC 中
- 保存新线程的原始 MDC 值,用于任务结束后恢复
- 任务执行完成后,恢复新线程的原始 MDC 状态
- 通过 try-finally 保证无论任务执行成功与否都能正确恢复
配置的 MDC key 列表:
TraceIdUtil.TRACE_ID_KEY- 消息追踪 ID
通过这种机制,即使在异步线程中,也能保证日志中的 TraceId 连续性,便于问题排查。
完整消息流转示例
上行消息分发场景
设备 A 发布消息到主题 sensor/temperature/data,设备 B 和服务 C 订阅了该主题。
设备 A ──► MQTT Broker (接入层)
│
▼
MessageEvent 发布到 Spring 事件总线
│
▼
MessageEventListener.handleMessageUpstream() [异步线程]
│
├── 从消息提取 InternalMessage
│
▼
dispatch(InternalMessage)
│
├── subscriptionManager.findMatchingSubscriptions(
│ "sensor/temperature/data"
│ )
│
└── 返回匹配的订阅列表: [设备B, 服务C]
│
▼
遍历订阅列表
│
├── 设备B: MessageSendDispatcher.send()
│ ├── 从 RegManager 获取设备B的 Channel
│ ├── 协议类型为 MQTT
│ ├── 构造 MqttPublishMessage
│ └── channel.writeAndFlush() ► 设备 B 收到消息
│
└── 服务C: MessageSendDispatcher.send()
├── 从 RegManager 获取服务C的 Channel
├── 协议类型为 TCP
├── 构造带换行符的纯文本消息
└── channel.writeAndFlush() ► 服务 C 收到消息
│
▼
发布 RuleEvent 给规则引擎处理
订阅管理场景
设备 B 发送 SUBSCRIBE 报文订阅主题 sensor/temperature/+
设备 B ──► MQTT Broker (接入层)
│
▼
DeviceSubscribeEvent (SUBSCRIBE 类型) 发布
│
▼
DeviceSubscribeEventListener.handleDeviceSubscribe()
│
├── 提取 clientId、topic、qos
│
└── subscriptionManager.subscribe(clientId, topic, qos)
│
├── 添加到 clientSubscriptions Map
│
└── topicMatcher.addSubscription(topic, clientId)
│
└── 将 "sensor/temperature/+" 添加到 Trie 树
│
└── subscriptionLogService.logSubscribe() ► 数据库保存操作记录
模块依赖关系
pom.xml 依赖声明
<dependency>
<groupId>com.jysemel.iot</groupId>
<artifactId>sample-00-iot-common</artifactId>
<!-- 提供 InternalMessage、EventPublisher、RedisKeys、TraceIdUtil 等通用能力 -->
</dependency>
<dependency>
<groupId>com.jysemel.iot</groupId>
<artifactId>sample-00-iot-access</artifactId>
<!-- 提供 RegManager 连接管理能力,用于查找设备 Channel -->
</dependency>
与其他模块的交互
与接入层的交互:
- 接收 MessageEvent:订阅接入层发布的消息事件
- 接收 DeviceSubscribeEvent:订阅接入层发布的设备订阅事件
- 调用 RegManager:根据 clientId 获取设备 Channel
与公共模块的交互:
- 使用 InternalMessage:统一消息载体
- 使用 EventPublisher:发布 MessageDispatchEvent 和 RuleEvent
- 使用 ModuleConstants:模块标识,用于日志标识
- 使用 TraceIdUtil:链路追踪,保持日志关联
配置项说明
核心配置项:
mqtt:
sub: inner # 订阅管理模式,inner=内存,redis=Redis
# 不配置时默认使用 inner 模式
使用 Redis 模式时,需要额外确保 Redis 连接配置正确,包括 host、port、password 等参数。
关键设计说明
Trie 树主题匹配
使用前缀树而非 HashMap 精确匹配,主要原因:
- MQTT 主题支持通配符,订阅主题可能是
sensor/+/temperature或device/# - 消息发布时需要找到所有匹配的订阅过滤器
- Trie 树按层级匹配,支持同时精确匹配和通配符匹配
- 时间复杂度 O(n),n 为主题层级深度,性能远优于遍历所有订阅逐一正则匹配
读写并发安全
- 订阅管理器使用 ConcurrentHashMap 存储,保证并发读写安全
- TopicMatcher 使用 HashMap 存储子节点,CopyOnWriteArrayList 存储订阅者
- 写操作较少,订阅/取消订阅的频率远低于消息匹配频率
- CopyOnWriteArrayList 在读多写少场景下提供优秀性能
Caffeine 本地缓存
Redis 模式下,使用 Caffeine 作为本地缓存层:
- 降低 Redis 网络请求频率,减少延迟
- 缓存有效期 10 分钟,保证最终一致性
- 写操作后主动清除缓存,避免读取脏数据
- maximumSize 限制缓存最大条目,防止内存溢出
异步线程与 TraceId 传递
消息分发使用独立线程池异步处理:
- 不阻塞接入层主线程,提高消息接收吞吐量
- 通过 TaskDecorator 复制 MDC 上下文,保持日志链路可追踪
- TraceId 从消息接收开始贯穿到分发结束,统一日志查询
多协议消息发送
MessageSendDispatcher 根据协议类型选择发送方式:
- MQTT 协议:封装为 MqttPublishMessage,支持 QoS、Retain 等 MQTT 特性
- TCP 协议:直接发送字节数组,添加换行符作为消息边界,便于客户端解析
- 设计预留扩展点,未来添加 CoAP / WebSocket 等协议支持
监控与日志
核心日志标识:
ModuleConstants.MODULE_ROUTING:统一前缀 "routing" 或对应模块名,便于日志过滤- TraceId:每条链路请求的唯一标识,贯穿消息接收、分发、转发全流程
关键日志场景:
- 订阅成功:记录 clientId、topic、qos,便于排查订阅关系
- 订阅去重:记录 WARN,发现重复订阅时拦截
- 分发统计:记录主题匹配到的订阅者数量,监控消息分发负载
- 设备离线:记录 WARN,设备离线为正常业务场景
- 发送失败:记录 ERROR,包含异常类型和目标 clientId
- Trie 树操作:记录 DEBUG 级别详细日志,开发调试用
使用指南
在 Spring Boot 应用中引入本模块:
-
在 pom.xml 中添加依赖:
com.jysemel.iot sample-00-iot-routing -
在 application.yml 中配置订阅管理模式(可选,默认 inner):
mqtt:
sub: inner # 或 redis -
确保事件总线可用(Spring 默认事件机制,无需额外配置)
-
消息发送示例(业务层):
// 构造 InternalMessage
InternalMessage message = new InternalMessage();
message.setTopic("command/device001");
message.setPayload("turn on".getBytes());
message.setQos(1);
message.setProtocol("MQTT");// 通过 MessageSendDispatcher 发送
messageSendDispatcher.send("device001", message);
扩展建议
增加订阅持久化
当前 Redis 模式下订阅数据存在过期时间(30 天),建议增加:
- 持久化存储层,如 MySQL 存储历史订阅
- 订阅恢复机制,服务重启后自动重建订阅索引
消息质量保障
当前 QoS > 0 的消息没有重传机制,建议增加:
- 未确认消息存储(In-Flight 队列)
- 定期重发未确认消息
- 重发次数限制与丢弃策略
限流与熔断
在分发器层面增加:
- 按 clientId 级别限流,防止某个订阅者消息过多影响其他
- 熔断机制,当某个订阅者发送失败次数达到阈值时暂停发送
订阅审计
增强订阅日志功能:
- 记录订阅者 IP 地址
- 记录订阅来源(客户端主动订阅/管理平台强制订阅)
- 统计订阅主题分布,用于热点分析
- 订阅变更历史可追溯
性能优化建议
Trie 树节点优化
当前 Trie 节点的 children 使用 HashMap,可考虑:
- 对高频率前缀使用数组直接索引,如
$SYS系统主题 - 对低频率前缀保持 HashMap,减少内存占用
- 增加节点合并逻辑,减少空节点
订阅查找缓存
findMatchingSubscriptions 调用频率较高,可优化:
- 对热门主题的查找结果增加短期缓存(几秒)
- 使用订阅变更事件触发缓存失效
- 降低 TopicMatcher 中递归查找的频率
批量消息分发
当前消息逐条发送,可考虑:
- 批量收集发送任务,按 Channel 聚合后批量写入
- 减少 Netty writeAndFlush 调用次数,提升 IO 效率
- 批量操作日志聚合输出
故障排查指南
问题:订阅后收不到消息
排查步骤:
- 检查 DeviceSubscribeEventListener 是否收到订阅事件
- 查看 subscriptionManager.isSubscribed() 返回值是否为 true
- 检查 TopicMatcher 中是否包含订阅过滤器
- 查看消息发布时 findMatchingSubscriptions 是否返回订阅者
- 确认设备 Channel 是否在线(通过 RegManager.getConnection())
- 检查 Netty 发送日志,确认消息是否写入 Channel
问题:消息发送失败但设备在线
排查步骤:
- 检查 MessageSendDispatcher 中的异常日志
- 确认 channel.isActive() 是否为 true
- 检查 Netty Pipeline 是否正确配置编码器
- 检查 payload 是否为空或格式异常
- 检查 QoS 级别与设备能力是否匹配
- 查看 TCP 抓包确认消息是否发送到网络
问题:Redis 模式下订阅丢失
排查步骤:
- 确认 Redis 服务是否正常运行
- 检查 RedisKey 是否正确(是否有命名空间隔离)
- 检查 Redis 过期时间是否被误设置为 0
- 确认本地缓存与 Redis 数据是否一致
- 查看是否有其他节点同时写入导致数据覆盖
问题:TraceId 不传递到异步线程
排查步骤:
- 确认 RoutingTraceIdTaskExecutorAsyncConfig 是否被 Spring 扫描
- 确认 @Async 注解是否正确指定了 bean 名称
- 检查 MDC_KEYS 常量中是否包含正确的 key
- 确认提交任务前 MDC 中已设置 TraceId
- 检查其他线程池是否干扰了 MDC 上下文
总结
sample-00-iot-routing 模块作为物联网平台的消息路由中枢,承担订阅关系维护与消息分发的核心职责。通过 Trie 树主题匹配、多模式订阅存储、多协议消息封装、异步线程池处理等设计,实现了高性能、可扩展、易维护的消息路由能力。
该模块与接入层松耦合,通过 Spring 事件机制交互,不依赖具体协议实现,可独立扩展新协议支持,为上层业务层提供统一的消息收发接口。