
物联网数据存储架构从时序数据库到时序关系型混合存储的选型复盘物联网存储的挑战不是存得下而是查得快——当你需要在1秒内从万亿条传感器数据中找到某个设备过去7天的所有异常温度点时存储架构的选择就决定了答案。一、问题的分层物联网平台的数据按属性天然分为两类数据类型特征写入模式查询模式数据量级时序数据时间戳数值不可变高频追加写时间范围设备ID每天TB级元数据设备属性/关系可变低频随机写关联查询/点查百万级记录这个划分是架构决策的基石。试图用MySQL同时解决两类问题就是大多数物联网项目性能崩塌的根源。二、时序数据库的深度对比我们在同一硬件环境3节点×32C64G×NVMe SSD上做了全面的基准测试。2.1 写入性能测试数据1000万设备每设备每10秒上报1条10字段持续1小时 TDengine 3.2: ████████████████████████████████ 12,800,000 points/sec InfluxDB 2.7: ██████████ 3,200,000 points/sec TimescaleDB: ██████ 1,900,000 points/sec ClickHouse: ██████████████████████ 8,500,000 points/sec2.2 查询延迟对比10亿条数据规模查询场景TDengineInfluxDBTimescaleDBClickHouse单设备24h数据8ms95ms120ms45ms1000设备最新值5ms220ms180ms35ms时间范围聚合(1h→1min降采样)120ms2800ms3400ms850ms全表扫描(max值)3200ms超时(30s)超时(30s)5800ms磁盘占用(压缩后)42GB128GB105GB68GB2.3 为什么选择TDengine5个决定性因素一个设备一张表的存储模型——同一设备的数据物理连续存储时间范围查询本质上是顺序读这是8ms延迟的物理基础。列式存储两级压缩——delta-of-delta时间戳压缩类Gorilla浮点压缩8:1的压缩比是实测数据。超级表STable抽象——既保留了每设备独立存储的优势又提供了跨设备的聚合查询能力。内置降采样——滚动窗口聚合不需要再写Flink作业。极低运维成本——3节点集群即可支撑每天TB级写入不像ClickHouse需要更多节点。三、关系型存储的MySQL建模设备元数据虽然数据量不大但模型复杂度不低-- 产品定义设备模板 CREATE TABLE product_definition ( id BIGINT PRIMARY KEY AUTO_INCREMENT, product_key VARCHAR(64) NOT NULL UNIQUE COMMENT 产品标识, product_name VARCHAR(128) NOT NULL, device_type ENUM(sensor,actuator,gateway,camera) NOT NULL, protocol_type ENUM(mqtt,coap,http,modbus,opcua) NOT NULL, data_format ENUM(json,cbor,binary,protobuf) NOT NULL DEFAULT json, -- TSL(Thing Specification Language) - 物模型定义 thing_model JSON NOT NULL COMMENT 属性/服务/事件定义, status TINYINT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) COMMENT 产品定义表; -- 设备实例 CREATE TABLE device_instance ( id BIGINT PRIMARY KEY AUTO_INCREMENT, device_id VARCHAR(64) NOT NULL UNIQUE COMMENT 设备唯一标识, device_name VARCHAR(128), product_id BIGINT NOT NULL, -- 设备密钥一机一密 device_secret VARCHAR(128) NOT NULL, -- 激活状态 activation_status ENUM(inactive,active,disabled,deleted) DEFAULT inactive, first_online_time DATETIME, last_online_time DATETIME, -- 设备属性冗余物模型中的关键字段避免JSON解析 firmware_version VARCHAR(32), ip_address VARCHAR(45), rssi INT COMMENT 信号强度, -- 地理位置 location POINT SRID 4326 COMMENT GPS坐标, geo_hash VARCHAR(12) COMMENT GeoHash(用于空间查询), -- 租户/分组 tenant_id BIGINT NOT NULL, group_id BIGINT, -- 标签JSON支持动态扩展 tags JSON, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_product (product_id), INDEX idx_tenant (tenant_id), INDEX idx_status (activation_status), SPATIAL INDEX idx_location (location), INDEX idx_geo_hash (geo_hash) ) COMMENT 设备实例表; -- 设备关系如传感器属于某个网关 CREATE TABLE device_relation ( id BIGINT PRIMARY KEY AUTO_INCREMENT, parent_device_id VARCHAR(64) NOT NULL COMMENT 父设备(网关), child_device_id VARCHAR(64) NOT NULL COMMENT 子设备(传感器), relation_type ENUM(topology,group,shadow) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_relation (parent_device_id, child_device_id, relation_type), INDEX idx_child (child_device_id) ) COMMENT 设备拓扑关系表;四、冷热数据分层存储这是控制成本的核心策略。不加以分层时序数据的存储成本会随设备数量线性增长2年内吃掉所有利润。Service public class TieredStorageManager { // 分层策略 private static final Duration HOT_RETENTION Duration.ofDays(7); // TDengine保留7天 private static final Duration WARM_RETENTION Duration.ofDays(90); // Parquet保留90天 // 90天自动归档到冷存储S3 Glacier类型 /** * 每日执行的数据迁移 */ Scheduled(cron 0 0 2 * * ?) // 每天凌晨2点 public void migrateData() { // 1. TDengine → Parquet7天→90天 migrateHotToWarm(); // 2. Parquet → S3 Glacier90天→永久 migrateWarmToCold(); // 3. 降采样原始数据 → 聚合摘要 downsampleOldData(); } private void migrateHotToWarm() { // 从TDengine导出第8天数据写入Parquet String sql SELECT ts, device_id, temperature, humidity, vibration_rms FROM sensor_data WHERE ts NOW - 8d AND ts NOW - 7d ; // 按设备ID分区写入Parquet每个文件10000行 try (ResultSet rs tdengineQuery(sql)) { ParquetWriter writer new ParquetWriter( /data/warm/date LocalDate.now().minusDays(8) /, CompressionCodecName.ZSTD // ZSTD压缩比最好 ); ListSensorRecord batch new ArrayList(10000); while (rs.next()) { batch.add(mapToRecord(rs)); if (batch.size() 10000) { writer.writeBatch(batch); batch.clear(); } } writer.close(); } // 迁移完成后删除TDengine热数据TDengine RETENTION自动处理 } /** * 降采样策略 * 原始数据10s采样 → 1分钟聚合 → 1小时聚合 → 1天聚合 */ private void downsampleOldData() { // 30天以上数据从1分钟聚合到1小时粒度 downsampleService.aggregate( sourceTable sensor_data_1m, targetTable sensor_data_1h, windowDuration Duration.ofHours(1), aggregations List.of(AVG, MIN, MAX, STDDEV), startDate LocalDate.now().minusDays(90), endDate LocalDate.now().minusDays(30) ); } }4.1 查询路由查询层需要感知数据在哪一层Service public class QueryRouter { public QueryResult query(QueryRequest request) { Instant queryStart request.getStartTime(); if (queryStart.isAfter(Instant.now().minus(HOT_RETENTION))) { // 全部在热存储直接查TDengine return tdengineQuery(request); } else if (queryStart.isAfter(Instant.now().minus(WARM_RETENTION))) { // 跨热温存储并行查询结果合并 CompletableFutureQueryResult hotFuture CompletableFuture.supplyAsync(() - tdengineQuery(request.restrictTo(HOT_RETENTION))); CompletableFutureQueryResult warmFuture CompletableFuture.supplyAsync(() - parquetQuery(request.before(HOT_RETENTION))); return hotFuture.thenCombine(warmFuture, QueryResult::merge).join(); } else { // 涉及冷存储返回降采样数据 提示 QueryResult result parquetQuery(request); result.setNote(查询范围超过90天返回降采样数据1小时粒度); return result; } } }五、总结物联网数据存储架构的选型核心是分层——按数据特性分层、按冷热分层、按粒度分层时序数据和元数据必须分离存储。时序数据库TDengine处理高频写入和时间范围聚合MySQL处理设备关系和属性查询。混用MySQL的结果就是写入瓶颈和查询超时。TDengine在当前是物联网时序场景的最优解——8:1的压缩比、一个设备一张表的物理模型、毫秒级的单设备查询这三个指标直接决定了系统的可行性和运营成本。冷热分层不是可选的优化手段而是架构设计的必选项。7天热数据TDengine 90天温数据Parquet 永久冷数据S3 Glacier 多级降采样这套策略让我们的存储成本控制在每月1.2万元而全量存TDengine的成本是8.6万元。数据的价值与查询频率正相关与存储成本负相关——分层的本质是让每一条数据待在它应该在的位置。