Skip to content

ThingsBoard MQTT Transport 源码分析 ​

正文与配图来自原稿。源码路径对齐分支 release-4.3。
相关:项目结构介绍 · application.yml 配置 · 自定义规则节点等

0、模块放在哪?(release-4.3 补充) ​

读 MQTT 传输建议分清三层:

路径作用
common/transport/mqtt业务主体:Adaptor、Handler、Session、IP 限流、MqttTransportService(Netty 服务)
netty-mqttMQTT 协议编解码 / 连接生命周期等底层
transport/mqtt独立微服务启动器(service.type=tb-transport 时单独进程)

包名(common/transport/mqtt)大致为:

text
org.thingsboard.server.transport.mqtt
  ├── adaptors/          # Json / Proto / 兼容适配器
  ├── limits/            # IpFilter、ProxyIpFilter、限流统计
  ├── session/           # DeviceSessionCtx、Gateway 等
  ├── MqttTransportService.java
  ├── MqttTransportHandler.java
  ├── MqttTransportContext.java
  └── MqttTransportServerInitializer.java

下文分析的类,默认都在 common/transport/mqtt。


1、消息转换适配器 adaptors ​

首先看 mqtt-transport 的模块结构:

mqtt-transport 模块结构

adaptors 相关包

adaptors: 主要是抽象 MQTT 设备上传数据的转换方式。

类结构关系 ​

Adaptor 类结构关系

MqttTransportAdaptor 接口里面定义了不同消息的转换方式:

MqttTransportAdaptor 接口

release-4.3 中常见实现:

  • JsonMqttAdaptor:JSON 载荷
  • ProtoMqttAdaptor:Protobuf 载荷
  • BackwardCompatibilityAdaptor:向后兼容适配器

BackwardCompatibilityAdaptor 主要作用是适配 JsonMqttAdaptor 和 ProtoMqttAdaptor:默认先用 JsonMqttAdaptor 做数据转换,如果转换失败再用 ProtoMqttAdaptor。

BackwardCompatibilityAdaptor

适配器如何注入? ​

ThingsBoard 抽象了一个 TransportContext(传输层上下文),用于传输层需要使用的对象。MqttTransportContext 继承了 TransportContext,同时注入了 JsonMqttAdaptor 和 ProtoMqttAdaptor:

MqttTransportContext 注入 Adaptor

系统默认使用 JsonMqttAdaptor 来转换,体现在哪里?

需要看 MqttTransportService 在启动 Netty 服务端时注入了 MqttTransportContext:

MqttTransportService 启动与 Context

然后将 MqttTransportContext 传到相关构造器,把数据转换对象存入 deviceSessionCtx:

Context 传入 Handler / Session

deviceSessionCtx 持有 adaptor


2、IP 过滤 ​

包含 IpFilter 和 ProxyIpFilter,里面实现的逻辑都一样。这两个类都是 InboundHandler,都继承了 ChannelInboundHandlerAdapter,所以都是 handler 处理器,需要在 channel 初始化时添加进去。详细可以搜索一下 AbstractRemoteAddressFilter 的作用。

IpFilter / ProxyIpFilter

进入 IpFilter 分析:当设备发送数据时,会触发这个 Handler,然后调用里面的 accept 做逻辑处理:

IpFilter.accept

获取到客户端的 IP,进入 context.checkAddress(remoteAddress),先判断配置(原稿写 application.yml,现多用环境变量,见 配置文)中是否开启 IP 速率限制(如 TB_TRANSPORT_IP_RATE_LIMITS_ENABLED):

checkAddress 与限流开关

假如开启了速率限制,再看下面的逻辑:

java
var stats = ipMap.computeIfAbsent(address.getAddress(), a -> new InetAddressRateLimitStats());
return !stats.isBlocked() || (stats.getLastActivityTs() + ipBlockTimeout < System.currentTimeMillis());

首先:

java
Map<InetAddress, InetAddressRateLimitStats> ipMap = new ConcurrentHashMap<>();

意思是:设备第一次发数据时,先按 IP 在 ipMap 查是否有 InetAddressRateLimitStats;没有就创建一个放进去。InetAddressRateLimitStats 类如下:

InetAddressRateLimitStats

第一次 !stats.isBlocked() 肯定为 true,再比较:

text
stats.getLastActivityTs() + ipBlockTimeout < System.currentTimeMillis()

ipBlockTimeout 是配置值。若返回 true 就继续,否则不再接收。

如果设备达到速率限制,会把 isBlocked() 改为 true,再通过 Netty 底层的 AbstractRemoteAddressFilter 拒绝接收,从而实现限制。


3、如何放开限制 ​

如果某一个设备的 IP 被限制了,如何放开?

在 DefaultTransportService 里启动了一个定时任务,一直去检查:

定时检查 IP 限制

然后将 IP 限制移除,或修改 blocked 状态:

解除 / 更新 blocked

(配置上还可关注 TB_TRANSPORT_MAX_WRONG_CREDENTIALS_PER_IP、TB_TRANSPORT_IP_BLOCK_TIMEOUT 等,见 MQTT 配置页。)


4、设备 Session 管理 ​

ThingsBoard 对设备 session 的抽象:

Session 抽象

release-4.3 中常见类型包括:

  • DeviceSessionCtx
  • MqttDeviceAwareSessionContext
  • Gateway / Sparkplug 相关 Session(网关、工业协议扩展)

deviceSessionCtx 的初始化:

deviceSessionCtx 初始化

Session 上会挂:设备身份、Adaptor、待发消息队列等,后续 CONNECT / PUBLISH 都围绕它展开。


5、MQTT 服务启动 ​

根据条件注解判断是否启动 MQTT(原稿表达式,与版本细节可能略有出入,以源码为准):

text
@ConditionalOnExpression(
  "'${service.type:null}'=='tb-transport'
   || ('${service.type:null}'=='monolith'
       && '${transport.api_enabled:true}'=='true'
       && '${transport.mqtt.enabled}'=='true')"
)

含义概要:

  • 独立 Transport 微服务(tb-transport),或
  • 单体(monolith)且开启 transport API 与 MQTT

满足则加载 MqttTransportService:

条件装配与启动类

这是一个标准的 Netty 服务端启动:Boss/Worker、Pipeline(解码、IpFilter、Ssl、MqttTransportHandler 等)。MQTT 接入基于 Netty 实现;协议编解码细节还可下钻 netty-mqtt 模块。


6、设备连接 ​

一切都从 channelRead 开始执行:

channelRead

release-4.3 中逻辑概要:若是成功解码的 MqttMessage,则调用 processMqttMsg(ctx, message)。

进入 processMqttMsg 后:

processMqttMsg 分支

CONNECT 流程会先验证设备 token(或其它凭证):

验证设备 Token

进入 onValidateDeviceResponse(msg, ctx, connectMessage),会与 tb-core 交互,需要把设备的 active 等状态置为在线:

校验通过后通知 Core、设备上线

鉴权通过后 Session 才算真正连上,后续才能处理 PUBLISH 等报文。


7、设备发送数据 ​

原稿此节标题已列出,正文未展开。下面按 release-4.3 的 MqttTransportHandler 补一条阅读路径(辅):

  1. channelRead → processMqttMsg
  2. 对 PUBLISH:进入 processPublish(ctx, mqttMsg)
  3. 先 checkConnected:未完成连接则直接返回
  4. 取 topicName / packetId
  5. 若 Topic 以网关 API 前缀开头(如 MqttTopics.BASE_GATEWAY_API_TOPIC),走 GatewaySessionHandler;否则走普通设备上报
  6. 通过 Session 上的 Adaptor(JSON / Proto / 兼容)把 payload 转成平台内部消息
  7. 经 TransportService / 队列 交给 Core / Rule Engine(入库、规则链等)

读代码时建议断点顺序:

text
MqttTransportHandler#channelRead
  → processMqttMsg
  → processPublish
  → JsonMqttAdaptor / ProtoMqttAdaptor(转换)
  → DefaultTransportService(上行到 Core)

设备侧 Topic 约定仍见官方 MQTT Device API(如遥测 v1/devices/me/telemetry)。


阅读路径小结 ​

text
启动: MqttTransportService(Netty)+ ConditionalOnExpression
  ↓
接入: IpFilter / ProxyIpFilter(可选 IP 限流)
  ↓
处理: MqttTransportHandler#channelRead → processMqttMsg
  ↓
连接: CONNECT → 校验 Token → onValidateDeviceResponse → Session / 设备 active
  ↓
上报: PUBLISH → processPublish → Adaptor 转换 → 队列 / Core

补充(辅) ​

  • 配置项见 MQTT Transport 配置 与官网 MQTT 配置页(MQTT_BIND_PORT、TB_TRANSPORT_IP_RATE_LIMITS_ENABLED 等)。
  • 网关设备、Sparkplug 在 session / gateway 包,比单设备路径更复杂,可第二轮再读。
  • 改 Adaptor 或 Handler 后需重新编译;打 jar / 镜像见专栏前文。

相关文章 ​

源码链接(release-4.3) ​