ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ThingsBoard集成TDengine:规则节点实现高并发时序数据存储

2026/10/5 3:51:44 拓冰建站 浏览量
ThingsBoard集成TDengine:规则节点实现高并发时序数据存储 1. 为什么要把 TDengine 集成到 ThingsBoard 里做物联网平台的人应该都绕不开 ThingsBoard。设备接入、规则引擎、可视化面板、RPC 下发这一整套能力做得相当完整社区活跃度也高拿来做设备管理和业务逻辑编排非常顺手。但真正跑起来以后大家会遇到同一个尴尬ThingsBoard 默认的存储方案扛不住高频时序数据。ThingsBoard 默认支持 H2 和 PostgreSQL 作为实体数据库Cassandra 作为时序数据库扩展。H2 本身就适合开发环境数据量一上来就吃力PostgreSQL 能撑一些但设备多了、采集频率高了以后写放大和查询延迟会变得很难看Cassandra 分布式能力强可运维成本不低很多中小团队没那个精力去维护一套 Cassandra 集群。这时候把 TDengine 引进来就顺理成章了。TDengine 是专门为物联网时序场景设计的数据库写入吞吐高、压缩比好、查询语法简单还自带超级表、连续查询、数据保留策略这些时序场景刚需能力。把 TDengine 接到 ThingsBoard 后面做遥测数据的最终存储相当于给平台加了一个专门吃时序数据的加速器设备管理继续用 ThingsBoard海量监控数据丢给 TDengine 去扛。这篇文章我把自己实际落地这套集成方案的过程完整梳理一遍包括版本选型、规则节点开发、规则链配置、问题排查和性能实测结果。不管你是刚开始调研架构选型还是已经踩了几个坑正在找解决方案这篇内容都值得花十分钟看完。2. 先说清楚ThingsBoard 里时序数据是怎么流的动手集成之前必须先理解 ThingsBoard 的运行时数据流。否则你会在规则链配置和日志排查的时候一头雾水。2.1 ThingsBoard 的核心模块与消息流转路径ThingsBoard 内部大致分成几个核心模块Transport 层负责接收设备上报包括 MQTT、CoAP、HTTP 这些协议Rule Engine 层负责处理消息做数据过滤、转换、存储、告警等逻辑Entity Service 层负责设备、资产、客户等实体管理存储层负责把遥测数据和实体数据落盘。设备上报一条遥测数据流程是这样的设备通过 MQTT 或 HTTP 把 JSON 数据发给 Transport。Transport 把数据封装成 ProtoBuf 消息投递到 Rule Engine 的消息队列。Rule Engine 根据规则链配置把消息交给各个规则节点处理。默认规则链中会有 Save Timeseries 节点把遥测数据写到 ThingsBoard 的时序存储里。前端面板或 API 查询时再从时序存储中读取数据。我们的目标就是在第 4 步做文章。要么替换掉默认的时序存储要么在规则链中插入一个自定义节点把遥测数据同时或直接写到 TDengine。2.2 默认存储方案在物联网场景下的瓶颈我第一次用 ThingsBoard 做项目时设备量大概 500 台每台 5 秒上报一次温湿度。用默认的 PostgreSQL 存储跑了两周就明显卡顿设备列表加载慢最新遥测值查询经常超过 3 秒历史曲线加载直接卡死。原因不复杂关系型数据库的行式存储和索引结构天然不擅长处理高频时序数据的写入。每台设备每次上报都是一次 INSERT UPSERT500 台设备 5 秒一轮就是每秒 100 次写入看起来不高但加上查询、聚合、实时面板刷新数据库很快就成为瓶颈。更重要的是时序数据有很强的按时间维度追加写入、按时间段范围查询特征关系型数据库并不理解这种数据模式存储利用率不高查询优化器也发挥不出优势。2.3 TDengine 在这条链路里的定位TDengine 在这套架构里承担的角色很纯粹时序数据仓库。设备元数据、告警记录、租户配置这些还是继续放 ThingsBoard 自己的数据库里但所有设备上报的遥测数据走 TDengine 来存、来查。这么做的好处很直接写入性能比 PostgreSQL 高出一个数量级官方宣称单机每秒能写几百万条记录就算打个折也足够中小规模项目用。数据压缩比非常好时序数据时间戳加数值的重复模式在 TDengine 的列式压缩下通常能压到原始数据的十分之一以下。查询语法专门为时序做了优化比如按时间窗口聚合、降采样、插值这些操作一条 SQL 就能搞定。超级表模型正好匹配物联网业务一张超级表代表一种设备类型或一个指标集合每台设备对应一张子表tag 可以存设备 ID、位置、类型等静态属性动态数值存到列里。所以最简单的理解方式是ThingsBoard 继续做它擅长的事连接管理、规则引擎、可视化TDengine 去做它擅长的事海量时序数据写入与查询各司其职。3. 环境准备与版本选型集成方案确认了接下来把环境搭起来。版本选型这里特别重要因为我见过不少人在这一步就掉坑里了。3.1 ThingsBoard 与 TDengine 版本兼容性怎么选先看 ThingsBoard。目前主流版本是 3.x 系列我这次用的是 3.4.4 社区版。ThingsBoard 的规则引擎 API 在 3.x 内相对稳定但不同小版本之间也有细微调整建议你用哪个版本就编译哪个版本的适配代码不要拿 3.2 的 jar 往 3.7 上丢。TDengine 这边3.x 是目前的主力版本跟 2.x 相比语法和底层架构都有一些变化。我推荐直接用 3.x 新版因为 2.x 已经进入维护期新特性都在 3.x 上。TDengine 官方提供了 JDBC 驱动 taos-jdbcdriver3.x 版本对应的是 3.x 的 JDBC 驱动这里也要匹配好。网上还有一些社区维护的 ThingsBoard TDengine 扩展包比如 thingsboard-extensions 里有人做过 TDengine 的存储节点。这些可以直接参考但要注意它对应的 TB 版本有的是基于 2.x 做的硬搬到 3.x 上可能会遇到类加载和 API 变更的问题。3.2 部署方式建议先单机后集群我这次用单机部署一台 8C16G 的服务器同时跑 ThingsBoard 和 TDengine。对绝大多数中小项目来说这个规模起步完全够用。TDengine 的集群能力是有的但引入集群意味着更多的节点、更复杂的运维必要性通常是数据量或者可用性要求到了某个阈值才出现。如果团队没有专职 DBA我强烈建议前期先用单机 TDengine配合数据保留策略把超过保留期的时间分区自动清理掉能顶住远比想象中大的数据量。真到了单机扛不住的时候再用 TDengine 的集群方案平滑扩容。表格整理一下我这次的环境参数组件版本说明操作系统Ubuntu 22.04 LTS64位内核 5.15JDKOpenJDK 11ThingsBoard 3.4 要求 JDK 11Maven3.8.x编译规则节点插件用ThingsBoard3.4.4 CE社区版自带规则引擎TDengine3.0.5服务端 taosdtaos-jdbcdriver3.2.7连接到 TDengine 的 JDBC 驱动Node.js16.x前端资源构建用改规则节点图标时可能需要3.3 前置组件安装踩坑记录TDengine 的安装本身不复杂在 Ubuntu 上直接装官方 deb 包就行装完用 systemctl 启动 taosd。这里有几个我踩过的坑值得提一下。第一装完 TDengine 后先在命令行验证一下。执行 taos 进入 CLI执行 show databases能正常显示数据库列表再往下走。如果 taos 命令连不上服务端八成是 taosd 没启动或者配置文件里的 FQDN 解析有问题。第二TDengine 3.x 的 JDBC 连接串跟 2.x 不太一样。3.x 的 taos-jdbcdriver 支持两种 URL 格式一种是 jdbc:TAOS://hostname:6030/dbname另一种是 HTTP 方式的 jdbc:TAOSWS://hostname:6041/rest/dbname。前者走原生 TCP 协议性能更好后者走 REST 接口穿透性更好。我这边的规则节点用原生 TCP 协议。第三ThingsBoard 首次启动会初始化数据库我第一次启动时等了大概两分钟以为卡死了其实是在建表。如果用的是默认 H2 模式启动日志里会看到 HikariPool 初始化相关的信息耐心等一下就好。ThingsBoard 安装完成后先确认基础功能可用。浏览器打开 8080 端口用系统管理员账号登录创建一个测试设备手动发送一条遥测数据确认默认规则链能正常存数据。基础通了再开始集成工作。4. 集成路线选型改底层存储还是加规则节点前面铺垫了这么多现在到了核心决策点。把 TDengine 集成到 ThingsBoard主流的做法有两条路线。4.1 方案一直接替换 ThingsBoard 的时序存储实现这个方案的做法是修改 ThingsBoard 的源码或者引入社区适配器让 ThingsBoard 内部的 TimeseriesService 接口实现指向 TDengine替代默认的 Cassandra 或 SQL 存储。优点是不用动规则链设备上行数据自动落到 TDengine 里查询遥测 API 也仍然走 ThingsBoard 自己的接口对上层应用透明。缺点是侵入性强你需要维护一个基于特定 TB 版本分支的代码TB 升级时适配工程可能很大。社区版的 ThingsBoard 没有官方 TDengine 适配器这个路线基本意味着要 fork 源码长期维护。4.2 方案二利用规则引擎开发自定义 TDengine 存储节点这个方案是在规则链里插入一个自定义规则节点专门负责把遥测数据写入 TDengine。不改 ThingsBoard 底层存储原有数据流完全不动只是新增一个数据出口。优点代码量小一个 Maven 工程就能搞定。对 ThingsBoard 侵入性几乎为零升级 TB 时基本不受影响。灵活性很高可以在规则节点里做字段映射、数据清洗、批量写入等操作。不破坏原有存储万一 TDengine 出问题默认存储还在不会造成数据链路全断。缺点需要在每个需要持久化的规则链里显式加入这个节点。修改规则链时必须理解 TB 消息格式对新手有一点学习成本。4.3 我为什么选了规则节点方案实际对比下来我选了规则节点方案。原因很现实我维护的 TB 实例还需要跟上社区版本升级不希望被一个深度改造的底层存储绑死。而且规则节点方案对数据流的控制粒度更细——比如有些设备上报的数据我不需要全部入库可以在规则链路里做一层过滤再交给 TDengine 节点有些设备的指标需要做单位转换也可以在节点里顺手完成。下面这张表可以更直观地对比两条路线对比项替换底层存储自定义规则节点开发量大需要改源码或深度适配小一个插件工程侵入性高TB 启动链路会受影响低不影响原有模块TB 升级兼容性差升级成本高好jar 包兼容即可数据流控制粒度粗所有遥测数据统一存储细可按设备/消息类型分流对 TDengine 特性利用依赖适配层实现可以在节点里直接写高级 SQL灵活性高运维风险底层故障影响全局节点故障只影响该规则链分支可熔断上表最后一行运维风险值得展开说一下。替换底层存储方案如果 TDengine 出了故障整个 ThingsBoard 的遥测读写链路就全挂了而自定义规则节点方案里即使 TDengine 节点抛异常规则链可以配置为将该分支标记为失败同时让消息继续走原有存储或者转发到告警节点做通知。这样做在关键时刻是可以救命的。5. 核心实操写一个 TDengine 规则节点插件方案定了正式开始写代码。这个过程我尽量讲得细一点从工程搭建到部署验证保证你照着做就能跑通。5.1 创建 Maven 工程与引入核心依赖新建一个标准的 Maven 工程JDK 版本 11打包方式 jar。pom.xml 里需要引入 ThingsBoard 的规则引擎 API 和 TDengine 的 JDBC 驱动。关键依赖如下dependencies !-- ThingsBoard 规则节点 API -- dependency groupIdorg.thingsboard/groupId artifactIdrule-engine-api/artifactId version3.4.4/version scopeprovided/scope /dependency dependency groupIdorg.thingsboard/groupId artifactIdcommon-message/artifactId version3.4.4/version scopeprovided/scope /dependency !-- TDengine JDBC 驱动 -- dependency groupIdcom.taosdata.jdbc/groupId artifactIdtaos-jdbcdriver/artifactId version3.2.7/version /dependency !-- JSON 解析 -- dependency groupIdcom.google.code.gson/groupId artifactIdgson/artifactId version2.8.9/version /dependency /dependencies注意 rule-engine-api 的 scope 设为 provided因为 ThingsBoard 服务端本身已经带了这些类。如果设成 compile打包时把 TB 的类也打进去部署时反而可能造成类冲突这种问题非常难排查。5.2 规则节点主类实现思路ThingsBoard 自定义规则节点的核心是一个继承自 TbAbstractNode 的类重写 onMsg 方法。消息进入节点后通过 TbMsg 对象可以拿到数据、元数据和设备信息。我这里实现的 TdengineSaveNode 逻辑是这样的从 TbMsg 的元数据中获取设备名称和设备 ID。解析 msg.getData() 中的 JSON这是一个键值对键是遥测字段名值是数值。把 JsonElement 解析成 Java 对象拼成 SQL 并执行插入。执行成功调用 tellSuccess失败调用 tellFailure。核心代码结构如下Slf4j RuleNode( name Save to TDengine, nodeClass TdengineSaveNode.class, configClazz TdengineConfig.class, nodeDescription Save device telemetry to TDengine, nodeDetails Parse device telemetry and insert into TDengine super table, uiResources {static/rulenode/rulenode-core-config.js}, configDirective tbActionNodeTdengineConfig, icon storage ) public class TdengineSaveNode extends TbAbstractNode { private TdengineConfig config; private Connection connection; Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config TbNodeConfigurationUtils.convert(configuration, TdengineConfig.class); try { Class.forName(com.taosdata.jdbc.TSDBDriver); connection DriverManager.getConnection(config.getJdbcUrl(), config.getUsername(), config.getPassword()); } catch (Exception e) { throw new TbNodeException(Failed to init TDengine connection, e); } } Override public void onMsg(TbContext ctx, TbMsg msg) { try (Statement stmt connection.createStatement()) { String deviceName msg.getMetaData().getValue(deviceName); JsonObject data new Gson().fromJson(msg.getData(), JsonObject.class); long ts System.currentTimeMillis(); StringBuilder sb new StringBuilder(); sb.append(INSERT INTO ).append(config.getDatabase()).append(.) .append(deviceName).append( USING ).append(config.getSuperTable()) .append( TAGS ().append(deviceName).append() ) .append((ts, ); // 拼接字段名 for (String key : data.keySet()) { sb.append().append(key).append(, ); } sb.setLength(sb.length() - 2); sb.append() VALUES ().append(ts).append(, ); // 拼接字段值 for (String key : data.keySet()) { JsonElement value data.get(key); sb.append(value.getAsString()).append(, ); } sb.setLength(sb.length() - 2); sb.append()); stmt.executeUpdate(sb.toString()); ctx.tellSuccess(msg); } catch (Exception e) { log.error(Failed to save telemetry to TDengine, e); ctx.tellFailure(msg, e); } } Override public void destroy() { if (connection ! null) { try { connection.close(); } catch (Exception ignored) {} } } }这段代码的核心逻辑不复杂但几个地方要特别说明。第一SQL 里为什么要用反引号包裹设备名和字段名因为 TDengine 的表名如果带中划线或特殊字符必须用反引号转义。设备名称经常是类似 sensor-001 这种格式不转义会直接语法报错。第二INSERT 语句使用了 TDengine 的自动建表语法。INSERT INTO table_name USING super_table TAGS (...) VALUES (...)这个语法的好处是如果子表不存在TDengine 会根据超级表定义自动建表。这样就不需要事先为每台设备手动建表设备接入后第一次上报就能自动落数据。第三批量操作的优化空间。上面代码是单条插入简单直接适合演示和低频率场景。真正的生产环境设备量大以后要改成批量写入。TDengine 支持多 VALUES 追加写入比如 INSERT INTO table1 VALUES (...), (...), (...)这样一次 SQL 就能写入多条记录性能提升是数量级的。规则节点里可以考虑维护一个内存队列攒够一定条数再批量刷到 TDengine。5.3 配置类定义TdengineConfig 这个类用于接收规则节点在控制台配置的参数。JSON 配置和 Java 对象之间的映射由 ThingsBoard 框架自动处理。Data public class TdengineConfig implements Serializable { private String jdbcUrl; private String username; private String password; private String database; private String superTable; }控制台里配置的时候填一个 JSON 对象就行{ jdbcUrl: jdbc:TAOS://localhost:6030, username: root, password: taosdata, database: thingsboard, superTable: device_telemetry }5.4 前端注册与图标配置ThingsBoard 控制台的规则节点列表是从前端资源里读取的。要让自定义节点在控制台显示出来需要注册这个节点的前端配置。这个过程包括在 jar 包里放一个 rulenode-core-config.js或者在 ThingsBoard 的 UI 扩展目录里加入一段配置。对小团队来说一个更省事的方式是直接用 ThingsBoard 自带的 script 节点在规则链里用 JavaScript 写一个函数解析消息后调用 TDengine REST API 写入。但这样做性能和可维护性都不如原生 Java 节点所以我最终没有采用。如果你将 jar 包放到 ThingsBoard 的 rule-engine 目录需要把 jar 包中的 uiResources 路径配置好。具体做法是在 src/main/resources 下面建 static/rulenode/rulenode-core-config.js 文件内容里注册节点定义。然后打包这个 js 文件会打到 jar 里ThingsBoard 启动时会自动扫描 classpath 下的 uiResources。5.5 打包与部署执行 mvn clean packagetarget 目录下会生成一个带依赖的 jar 包。如果使用 maven-shade-plugin可以把所有依赖打成一个 fat jar这样部署时不需要额外拷贝 TDengine JDBC 驱动。部署步骤把 jar 复制到 ThingsBoard 安装目录的 extensions 目录或者对应版本的 rule-engine 外部扩展目录。重启 ThingsBoard 服务。查看日志确认没有 ClassNotFoundException 或 BeanCreationException。如果日志里报找不到 TdengineSaveNode 类九成是 jar 包路径没被扫描到。可以检查 thingsboard.conf 里是否配置了 extensions 目录或者直接把 jar 扔到 lib 目录下再重启。部署完成后打开 ThingsBoard 控制台的规则引擎页面在节点列表里搜索 TDengine能看到这个节点就说明注册成功了。6. ThingsBoard 控制台规则链配置与数据验证节点开发完成只是第一步真正要在业务中生效需要在控制台上把规则链配好。6.1 创建专用规则链打开 ThingsBoard 控制台进入规则链页面创建一个名为TDengine 遥测存储的新规则链。这个规则链的入口节点用消息类型切换节点Message Type Switch判断消息类型。如果是 POST_TELEMETRY就路由到 TDengine 保存节点其他类型消息可以路由到默认成功节点忽略掉或者继续往下走别的处理。连接线配置的核心思路是Input(root) - 消息类型切换 - POST_TELEMETRY - Save to TDengine - Success - 其他类型 - Success设备发的遥测消息进入这个规则链后走 TDengine 保存分支如果不是遥测消息直接成功结束不产生额外逻辑。6.2 设备与规则链的绑定方式规则链建好以后需要让设备消息走这条链。有两种做法第一种在设备配置里直接指定规则链。打开设备详情页在规则链字段选择刚才创建的TDengine 遥测存储规则链。这样该设备的所有上行消息都会进这条链。第二种在默认规则链里加一个规则节点做设备名称或设备 profile 的判断命中的消息转发给自定义规则链。这种方式适合多类设备共用一条主链、按设备类型分流到不同存储链路的场景。我这次测试用的方式更直接新建了一个独立的设备 Profile在这个 Profile 里绑定规则链。这样这个 Profile 下的所有设备都自动走 TDengine 存储链路跟默认 Profile 隔离开测试和上线都很可控。6.3 从发送遥测到 TDengine 落地全链路验证设备侧用 MQTT 客户端模拟上报发一条温湿度数据mosquitto_pub -d -q 1 \ -h localhost \ -p 1883 \ -t v1/devices/me/telemetry \ -u ACCESS_TOKEN \ -m {\temperature\: 23.5, \humidity\: 61.2}发完以后去 TDengine 命令行查数据taos use thingsboard; select * from device_telemetry order by ts desc limit 10;如果一切正常应该能看到刚才那台设备的温度湿度数据。这里有一个新手最容易犯的错误——设备名如果包含特殊字符自动建表时表名会被处理成带反引号的名称查询时不想加反引号可能查不到。我在测试中第一次写数据就遇到一个典型问题TDengine 3.x 的超级表字段映射要求第一列必须是时间戳列名如果不是 ts需要在创建超级表时指定 TIMESTAMP 关键字。我当时把时间戳字段叫 ts正好符合默认要求所以没碰到这个问题。如果你在设计超级表时用了 record_time 之类的时间列名写 JDBC 时要注意对应关系。7. 实战中遇到过的问题与排查方案集成过程中一定会遇到各种问题我把几个高概率踩坑的场景单独列出来方便你排查时对照。7.1 规则节点报错导致遥测消息失败积压现象是控制台规则链的节点上出现红色失败计数设备遥测数据没有写入 TDengine。第一步先看 ThingsBoard 的日志搜索 TdengineSaveNode 相关输出。最常见的错误有两个一是 JDBC 连接失败。检查 jdbcUrl 是否写对root 密码是否是默认的 taosdataTDengine 服务是否正常运行。测试环境经常会遇到 ThingsBoard 所在机器的 6030 端口没打通的情况防火墙规则要确认放行。二是 SQL 执行错误。错误消息里通常会带上具体的 SQL你可以复制这条 SQL 到 taos CLI 手动执行看看报什么错。常见的是字段类型不匹配比如 TDengine 超级表里某个列是 FLOAT但上报是字符串或者字段名跟保留字冲突。解决办法检查超级表的 schema 和上报的 JSON key 是否一一对应。用 try-catch 包裹整段逻辑失败后将原始 TbMsg 转寄到下一个节点或日志节点方便定位。在规则节点配置里增加 batch size 参数控制批量写入节奏。7.2 TDengine 报错 error (0x83a): query denied by license: external query is restricted这是很多人在网上问的问题我也踩到了。这个报错的意思是当前 TDengine 安装实例的外部查询被 license 限制了。具体原因要看你的 TDengine 版本和安装方式。社区版在部分版本或特殊配置下会限制外部应用程序通过 JDBC 访问数据你通过 taos CLI 本地查没问题但远端连 JDBC 就会报这个错。排查思路先用 root 登录 taos CLI执行 show licenses看一下当前实例的 license 状态包括到期时间、版本类型、是否允许外部连接。如果确实是 license 限制最直接的解决方式是确认你安装的版本是否适合生产使用。普通测试可以先检查 taos.cfg 里有没有开启某些限制参数比如 supportVnodes、queryPolicy 等。如果确认是试用授权或者社区版限制就需要评估是否升级到不受限的版本或者调整接入方式。比如 taos-jdbcdriver 切换到 REST 连接方式有时 REST 方式可以绕过原生连接的限制。这个问题最大的坑在于本地 CLI 查着一切正常让很多人误以为 TDengine 没问题结果问题全出在外部连接限制上。7.3 时间戳精度不一致导致的数据错位ThingsBoard 内部时间戳是毫秒TDengine 3.x 的时间戳默认精度也是毫秒所以直接对接不会有大问题。但如果你遇到写入以后查询出来时间偏移了 8 小时那就是时区问题。处理方案是在 TDengine 服务端配置里统一时区或者在 JDBC 连接串上带 timezone 参数jdbc:TAOS://localhost:6030?timezoneUTC注意这里说的是数据库时区不是服务器系统时区。我建议让 ThingsBoard、TDengine、应用服务器全部统一到 UTC展示层再转成本地时区。这样数据库存储的时间戳永远是无歧义的避免夏令时或服务器时区设置不一致造成的混乱。7.4 设备量大之后 TDengine 连接被打满规则节点里如果每来一条消息就创建一个 connection设备量稍微上来一点TDengine 的连接数就会飙到几百最终触发连接数限制系统开始报 too many connections。解决办法使用连接池比如 HikariCP给 TDengine 的 JDBC 连接做池化。节点内部用队列聚合数据批量写入降低连接占用频率。如果单台 TDengine 连接依然吃紧考虑部署多个 TDengine 节点按设备分组分库。在实际项目中我用 HikariCP 把最大连接数限制在 10然后用批量写入方式每秒几千条遥测消息也没把连接打满过。8. 实测效果数据写入与查询性能记录篇幅允许把我实测的一组数据放出来给准备上生产环境的朋友一个参考。8.1 测试环境与数据规模服务器配置CPU8 核内存16 GB磁盘SSD 500 GBOSUbuntu 22.04ThingsBoard3.4.4 社区版TDengine3.0.5模拟场景1000 台设备每 10 秒上报一次温度、湿度、电压三个指标也就是每秒 100 条消息、300 个指标点连续测试 24 小时8.2 性能结果TDengine 这边写入没有压力CPU 占用一直很低整个测试期间 TDengine 的 CPU 占用基本在 5% 以下。查询方面单设备 1 小时历史数据秒级返回聚合查询比如 1000 台设备全量 1 天平均值大约 2 秒内出结果。注意我在规则节点里做了批量组装每次攒满 200 条再执行一次 JDBC 批量写入而不是每条消息一条 SQL。这个优化把对 TDengine 的写入压力降了一个数量级。在同样的体量下原有默认 PostgreSQL 存储写入没问题但最新值查询偶尔会超过 3 秒历史聚合查询直接不可用和 TDengine 的差距非常明显。8.3 ThingsBoard 与 TDengine 资源占用对比部署 TDengine 后需要为它预留内存。TDengine 会根据数据量和缓存配置占用内存我的配置里让它用到大约 2 GB剩下的留给 ThingsBoard 的 JVM 和操作系统。ThingsBoard 的 JVM 堆我调到 4 GB整体跑下来没有物理内存不足的问题。9. 后续可以继续扩展的方向TDengine 接入 ThingsBoard 只是第一步做完了以后你会发现整个链路继续深入优化的空间非常大。9.1 利用 TDengine 超级表和连续查询做自动聚合在 TDengine 里建超级表时可以设计好标签字段比如 device_id、device_type、region。这样不用在应用层做分组统计直接用 SQL 就能按标签做多维度聚合。TDengine 的连续查询还可以自动周期性地做降采样比如每分钟计算一次过去一分钟的平均温度结果写到另一张表中。前端展示小时级曲线时直接查这个降采样表响应速度会快很多。这些事情不需要在 ThingsBoard 规则链里写代码完全是数据库层的能力。9.2 用 Grafana 对接 TDengine 做可视化面板ThingsBoard 自带的可视化对于实时面板足够用但做复杂的时序图表Grafana 生态更成熟。TDengine 官方有 Grafana 插件配置一个数据源以后可以直接用 SQL 查询 TDengine 里的数据通过 Grafana 做聚合图表、告警通知、大屏展示。这样 ThingsBoard 负责设备接入和业务规则Grafana 负责长时间跨度数据分析和监控展示各取所长。9.3 通过规则链将 TDengine 数据接入下游流处理如果后续要做实时告警、设备故障预测这类功能可以考虑在 TDengine 节点后面再接 Kafka 或者 Flink。ThingsBoard 规则链的节点本来就是可以串起来用的TDengine 节点把数据持久化以后再通过一个 Kafka 节点把原始消息转发给下游流处理任务。这样一来不只是把 TDengine 当作一个存储终端而是让数据以 TDengine 为锚点继续在数据链路里流动起来。9.4 多实例 ThingsBoard 共用一套 TDengine如果业务规模增长到需要多套 ThingsBoard 实例做水平扩展或者多地域部署可以让这些实例通过规则节点写入同一个 TDengine 集群。TDengine 作为中心化时序数据湖各节点独立上报查询统一走 TDengine。这个架构比直接同步 ThingsBoard 底库要轻量得多也是我比较推荐的一种演进方式。10. 集成完成后的维护建议最后再分享一些上线之后实际维护过程中沉淀下来的经验。TDengine 的版本升级要谨慎但是在测试环境先验证新的 taos-jdbcdriver 与 ThingsBoard 规则节点的兼容性再逐步更新生产环境。TDengine 3.x 内部升级相对平滑跨大版本就得多做一轮验证。规则链的改动建议先在独立规则链上测试确认 TDengine 写入正常后再把设备 Profile 切过去。我在测试环境验证好后是先在少量设备上灰度跑了半天没有异常才把全量设备切过去的。TDengine 的数据保留策略一定要提前设置。时序数据无限增长如果不做保留策略一年以后磁盘会被历史数据占满。在创建超级表时通过 KEEP 参数指定数据保留天数比如 90 天TDengine 会自动淘汰过期数据。还有一个细节是 TDengine 的 WAL 和缓存参数对写入性能影响很大。如果设备的写入量比较大可以把 wal_level 调低提升写入吞吐代价是异常断电时可能丢失少量最近数据。这个参数要根据业务对数据可靠性的要求来取舍。我自己最喜欢的 TDengine 特性之一是其写入稳定性。在一段时间的持续运行中TDengine 几乎没有出现过异常退出的情况而原先 PostgreSQL 方案在高频写入下偶尔会出现锁等待和连接池耗尽。就凭这一点当时花力气做这个集成就完全值回票价。如果你还在纠结要不要把 TDengine 集成到 ThingsBoard我的建议是如果你的设备数量超过几百台、上报频率超过每分钟一次、要做长时间范围历史分析那就别犹豫了集成成本远低于后面数据量大了再迁移的成本。按这篇文章的步骤操作一两天就能跑通。