1. 为什么要在若依框架中整合TDengine时序库最近在做一个物联网项目时遇到了一个典型问题设备每秒钟都会上报状态数据MySQL虽然能存但查询效率随着数据量增长直线下降。这时候同事推荐了国产时序数据库TDengine实测写入性能比MySQL高出一个数量级查询速度更是快了近百倍。这让我意识到在若依这类主流Java框架中整合时序库对物联网、监控系统等高频数据场景实在太有必要了。若依框架本身支持多数据源配置但默认只针对同构数据库比如主从MySQL。当我们需要接入TDengine这种异构数据库时会遇到几个关键问题驱动选择影响性能、连接池参数需要特别优化、MyBatis映射要注意语法差异。我在实际项目中踩过不少坑比如最初用RESTful驱动导致写入延迟高后来切到原生JDBC才解决还有一次因为没调整连接池maxWait参数导致高并发时请求堆积。时序数据的特点很鲜明写多读少、极少更新删除、按时间范围查询为主。TDengine针对这些场景做了深度优化比如一个设备一天产生86400条记录在MySQL需要86400行而TDengine用超级表子表结构可能只需要1行。这种存储方式带来的性能提升在千万级数据量时尤为明显。2. 环境准备与驱动选择2.1 Maven依赖配置在ruoyi-admin模块的pom.xml中添加TDengine驱动依赖时要注意版本匹配问题。我遇到过3.0.0驱动连不上TDengine 2.6服务端的情况后来统一升级到3.2.3才解决。建议根据服务端版本选择驱动!-- 原生JDBC驱动高性能首选 -- dependency groupIdcom.taosdata.jdbc/groupId artifactIdtaos-jdbcdriver/artifactId version3.2.3/version /dependency !-- RESTful驱动免安装客户端 -- dependency groupIdcom.taosdata.jdbc/groupId artifactIdtaos-jdbcdriver-rest/artifactId version3.2.3/version /dependency原生驱动需要服务器安装taos客户端库开发环境如果是Windows需要下载TDengine-client-3.x.x.exe安装。我在Mac上通过brew安装时遇到符号链接问题最终用brew install taosdata/repo/taos才成功。2.2 驱动类型性能对比在压力测试中发现两种驱动差异明显指标原生JDBC驱动RESTful驱动写入吞吐量1.2万条/秒3000条/秒查询延迟(P99)8ms35msCPU占用率15%40%如果只是少量设备上报100台用RESTful驱动更方便。但像我们项目有500设备并发写入必须用原生驱动。有个坑要注意原生驱动的socketTimeout默认是0无限等待在高负载时建议设置为30秒// JDBC URL参数示例 jdbc:TAOS://127.0.0.1:6030/db?charsetUTF-8socketTimeout300003. 多数据源配置实战3.1 application.yml配置详解若依的多数据源开关slave.enabled要设为true但TDengine的配置与MySQL有三大不同驱动类名com.taosdata.jdbc.TSDBDriver原生或com.taosdata.jdbc.rs.RestfulDriverREST验证SQLTDengine用select server_status()MySQL用select 1URL格式原生协议是jdbc:TAOS://REST是jdbc:TAOS-RS://完整配置示例spring: datasource: druid: slave: enabled: true url: jdbc:TAOS://192.168.1.100:6030/iot_db username: root password: taosdata driver-class-name: com.taosdata.jdbc.TSDBDriver validation-query: select server_status() initialSize: 10 # 比MySQL更大的初始连接 minIdle: 20 # 时序库查询突发量大 maxActive: 50 # 写入并发可能很高 maxWait: 3000 # 比MySQL更短的等待超时3.2 连接池参数调优经验TDengine的连接池配置要与业务场景匹配。我们经过压测得出以下经验值设备上报场景initialSize设备数/10不低于5maxActive设备数/2监控大屏查询minIdle要大于并发查询用户数maxWait建议设3000ms以下避免请求堆积遇到过一个典型问题某次活动导致查询量突增连接池瞬间打满。后来我们增加了动态扩容机制Bean ConfigurationProperties(spring.datasource.druid.slave) public DruidDataSource slaveDataSource() { DruidDataSource ds new DruidDataSource(); ds.setBreakAfterAcquireFailure(true); // 关键参数 ds.setTimeBetweenConnectErrorMillis(10000); return ds; }4. 业务层实现细节4.1 数据源切换的坑若依的DataSource注解用起来简单但要注意作用域问题。有次我在Controller类上加了DataSource(SLAVE)导致所有方法都走TDengine包括那些需要事务管理的写操作。正确做法是Service public class DeviceDataServiceImpl implements DeviceDataService { // 读操作走TDengine DataSource(DataSourceType.SLAVE) public ListDeviceLog queryRealtimeData(Long deviceId) { return mapper.selectLastHourData(deviceId); } // 写操作走MySQL DataSource(DataSourceType.MASTER) Transactional public void saveDeviceConfig(DeviceConfig config) { mysqlMapper.insertConfig(config); } }4.2 MyBatis映射特殊处理TDengine的SQL语法与MySQL大部分兼容但有些特殊点超级表查询需要指定时间范围避免全表扫描插入语法INSERT INTO ... USING ... TAGS ... VALUES ...不支持自动主键必须显式设置useGeneratedKeysfalseMapper配置示例select idselectPowerStats resultMappowerResult SELECT avg(voltage) as avg_voltage FROM power_data WHERE ts #{startTime} AND ts #{endTime} INTERVAL(1h) /select insert idinsertBatch useGeneratedKeysfalse INSERT INTO #{subTable} USING devices TAGS(#{deviceId}) VALUES(#{ts}, #{voltage}, #{current}) /insert5. 性能优化实战技巧5.1 写入批处理方案单条写入TDengine性能很差我们测试过批量大小吞吐量条/秒180010015000100045000最终采用的批处理方案Slf4j Service public class TaosBatchService { private static final int BATCH_SIZE 500; private ListDeviceData buffer new ArrayList(BATCH_SIZE); Async(taskExecutor) public void addToBatch(DeviceData data) { synchronized (buffer) { buffer.add(data); if(buffer.size() BATCH_SIZE) { flush(); } } } private void flush() { try { String sql buildBatchSql(buffer); jdbcTemplate.execute(sql); buffer.clear(); } catch (Exception e) { log.error(批量写入失败, e); } } }5.2 查询缓存策略TDengine虽然查询快但对热点数据还是加了Redis缓存。我们的缓存策略实时数据缓存5秒用Cacheable(cacheNamesrealtime, key#deviceId, unless#result null)历史统计缓存1小时用CachePut主动更新设备元信息缓存24小时通过消息队列通知更新缓存穿透防护方案public DeviceStats getDeviceStatsWithCache(Long deviceId) { String cacheKey stats: deviceId; DeviceStats stats redisTemplate.opsForValue().get(cacheKey); if(stats NULL_OBJECT) { // 特殊空值标记 return null; } if(stats null) { stats taosMapper.selectStats(deviceId); if(stats null) { redisTemplate.opsForValue().set(cacheKey, NULL_OBJECT, 5, TimeUnit.MINUTES); return null; } redisTemplate.opsForValue().set(cacheKey, stats, 1, TimeUnit.HOURS); } return stats; }6. 生产环境注意事项6.1 监控指标配置我们通过Spring Boot Actuator监控关键指标management: endpoints: web: exposure: include: health,metrics,druid metrics: tags: application: ${spring.application.name}重点关注的指标druid.datasource.slave.activeCount活跃连接数taos.queries.count查询QPStaos.writes.latency写入延迟6.2 故障排查经验遇到过两个典型问题连接泄漏因没有正确关闭ResultSet导致解决方案try (Connection conn dataSource.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql)) { // 处理结果 }时区问题TDengine默认UTC时间需要在连接参数指定jdbc:TAOS://127.0.0.1:6030/db?timezoneAsia/Shanghai7. 扩展应用场景除了物联网这套架构还适用于运维监控系统存储服务器性能指标金融交易分析高频交易数据存储智能家居设备状态历史记录最近我们还在TDengine上实现了实时告警功能通过创建流计算CREATE STREAM IF NOT EXISTS alert_stream TRIGGER WINDOW_CLOSE INTO alert_events AS SELECT * FROM device_data WHERE voltage 250 OR current 10 INTERVAL(10s);
若依框架多数据源架构下整合TDengine时序库实战
1. 为什么要在若依框架中整合TDengine时序库最近在做一个物联网项目时遇到了一个典型问题设备每秒钟都会上报状态数据MySQL虽然能存但查询效率随着数据量增长直线下降。这时候同事推荐了国产时序数据库TDengine实测写入性能比MySQL高出一个数量级查询速度更是快了近百倍。这让我意识到在若依这类主流Java框架中整合时序库对物联网、监控系统等高频数据场景实在太有必要了。若依框架本身支持多数据源配置但默认只针对同构数据库比如主从MySQL。当我们需要接入TDengine这种异构数据库时会遇到几个关键问题驱动选择影响性能、连接池参数需要特别优化、MyBatis映射要注意语法差异。我在实际项目中踩过不少坑比如最初用RESTful驱动导致写入延迟高后来切到原生JDBC才解决还有一次因为没调整连接池maxWait参数导致高并发时请求堆积。时序数据的特点很鲜明写多读少、极少更新删除、按时间范围查询为主。TDengine针对这些场景做了深度优化比如一个设备一天产生86400条记录在MySQL需要86400行而TDengine用超级表子表结构可能只需要1行。这种存储方式带来的性能提升在千万级数据量时尤为明显。2. 环境准备与驱动选择2.1 Maven依赖配置在ruoyi-admin模块的pom.xml中添加TDengine驱动依赖时要注意版本匹配问题。我遇到过3.0.0驱动连不上TDengine 2.6服务端的情况后来统一升级到3.2.3才解决。建议根据服务端版本选择驱动!-- 原生JDBC驱动高性能首选 -- dependency groupIdcom.taosdata.jdbc/groupId artifactIdtaos-jdbcdriver/artifactId version3.2.3/version /dependency !-- RESTful驱动免安装客户端 -- dependency groupIdcom.taosdata.jdbc/groupId artifactIdtaos-jdbcdriver-rest/artifactId version3.2.3/version /dependency原生驱动需要服务器安装taos客户端库开发环境如果是Windows需要下载TDengine-client-3.x.x.exe安装。我在Mac上通过brew安装时遇到符号链接问题最终用brew install taosdata/repo/taos才成功。2.2 驱动类型性能对比在压力测试中发现两种驱动差异明显指标原生JDBC驱动RESTful驱动写入吞吐量1.2万条/秒3000条/秒查询延迟(P99)8ms35msCPU占用率15%40%如果只是少量设备上报100台用RESTful驱动更方便。但像我们项目有500设备并发写入必须用原生驱动。有个坑要注意原生驱动的socketTimeout默认是0无限等待在高负载时建议设置为30秒// JDBC URL参数示例 jdbc:TAOS://127.0.0.1:6030/db?charsetUTF-8socketTimeout300003. 多数据源配置实战3.1 application.yml配置详解若依的多数据源开关slave.enabled要设为true但TDengine的配置与MySQL有三大不同驱动类名com.taosdata.jdbc.TSDBDriver原生或com.taosdata.jdbc.rs.RestfulDriverREST验证SQLTDengine用select server_status()MySQL用select 1URL格式原生协议是jdbc:TAOS://REST是jdbc:TAOS-RS://完整配置示例spring: datasource: druid: slave: enabled: true url: jdbc:TAOS://192.168.1.100:6030/iot_db username: root password: taosdata driver-class-name: com.taosdata.jdbc.TSDBDriver validation-query: select server_status() initialSize: 10 # 比MySQL更大的初始连接 minIdle: 20 # 时序库查询突发量大 maxActive: 50 # 写入并发可能很高 maxWait: 3000 # 比MySQL更短的等待超时3.2 连接池参数调优经验TDengine的连接池配置要与业务场景匹配。我们经过压测得出以下经验值设备上报场景initialSize设备数/10不低于5maxActive设备数/2监控大屏查询minIdle要大于并发查询用户数maxWait建议设3000ms以下避免请求堆积遇到过一个典型问题某次活动导致查询量突增连接池瞬间打满。后来我们增加了动态扩容机制Bean ConfigurationProperties(spring.datasource.druid.slave) public DruidDataSource slaveDataSource() { DruidDataSource ds new DruidDataSource(); ds.setBreakAfterAcquireFailure(true); // 关键参数 ds.setTimeBetweenConnectErrorMillis(10000); return ds; }4. 业务层实现细节4.1 数据源切换的坑若依的DataSource注解用起来简单但要注意作用域问题。有次我在Controller类上加了DataSource(SLAVE)导致所有方法都走TDengine包括那些需要事务管理的写操作。正确做法是Service public class DeviceDataServiceImpl implements DeviceDataService { // 读操作走TDengine DataSource(DataSourceType.SLAVE) public ListDeviceLog queryRealtimeData(Long deviceId) { return mapper.selectLastHourData(deviceId); } // 写操作走MySQL DataSource(DataSourceType.MASTER) Transactional public void saveDeviceConfig(DeviceConfig config) { mysqlMapper.insertConfig(config); } }4.2 MyBatis映射特殊处理TDengine的SQL语法与MySQL大部分兼容但有些特殊点超级表查询需要指定时间范围避免全表扫描插入语法INSERT INTO ... USING ... TAGS ... VALUES ...不支持自动主键必须显式设置useGeneratedKeysfalseMapper配置示例select idselectPowerStats resultMappowerResult SELECT avg(voltage) as avg_voltage FROM power_data WHERE ts #{startTime} AND ts #{endTime} INTERVAL(1h) /select insert idinsertBatch useGeneratedKeysfalse INSERT INTO #{subTable} USING devices TAGS(#{deviceId}) VALUES(#{ts}, #{voltage}, #{current}) /insert5. 性能优化实战技巧5.1 写入批处理方案单条写入TDengine性能很差我们测试过批量大小吞吐量条/秒180010015000100045000最终采用的批处理方案Slf4j Service public class TaosBatchService { private static final int BATCH_SIZE 500; private ListDeviceData buffer new ArrayList(BATCH_SIZE); Async(taskExecutor) public void addToBatch(DeviceData data) { synchronized (buffer) { buffer.add(data); if(buffer.size() BATCH_SIZE) { flush(); } } } private void flush() { try { String sql buildBatchSql(buffer); jdbcTemplate.execute(sql); buffer.clear(); } catch (Exception e) { log.error(批量写入失败, e); } } }5.2 查询缓存策略TDengine虽然查询快但对热点数据还是加了Redis缓存。我们的缓存策略实时数据缓存5秒用Cacheable(cacheNamesrealtime, key#deviceId, unless#result null)历史统计缓存1小时用CachePut主动更新设备元信息缓存24小时通过消息队列通知更新缓存穿透防护方案public DeviceStats getDeviceStatsWithCache(Long deviceId) { String cacheKey stats: deviceId; DeviceStats stats redisTemplate.opsForValue().get(cacheKey); if(stats NULL_OBJECT) { // 特殊空值标记 return null; } if(stats null) { stats taosMapper.selectStats(deviceId); if(stats null) { redisTemplate.opsForValue().set(cacheKey, NULL_OBJECT, 5, TimeUnit.MINUTES); return null; } redisTemplate.opsForValue().set(cacheKey, stats, 1, TimeUnit.HOURS); } return stats; }6. 生产环境注意事项6.1 监控指标配置我们通过Spring Boot Actuator监控关键指标management: endpoints: web: exposure: include: health,metrics,druid metrics: tags: application: ${spring.application.name}重点关注的指标druid.datasource.slave.activeCount活跃连接数taos.queries.count查询QPStaos.writes.latency写入延迟6.2 故障排查经验遇到过两个典型问题连接泄漏因没有正确关闭ResultSet导致解决方案try (Connection conn dataSource.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql)) { // 处理结果 }时区问题TDengine默认UTC时间需要在连接参数指定jdbc:TAOS://127.0.0.1:6030/db?timezoneAsia/Shanghai7. 扩展应用场景除了物联网这套架构还适用于运维监控系统存储服务器性能指标金融交易分析高频交易数据存储智能家居设备状态历史记录最近我们还在TDengine上实现了实时告警功能通过创建流计算CREATE STREAM IF NOT EXISTS alert_stream TRIGGER WINDOW_CLOSE INTO alert_events AS SELECT * FROM device_data WHERE voltage 250 OR current 10 INTERVAL(10s);