工业时序数据中台架构演进:从 IoTDB + MySQL 到去 DWD 层简化实践

发布时间:2026/8/11 4:52:11
工业时序数据中台架构演进:从 IoTDB + MySQL 到去 DWD 层简化实践 工业时序数据中台架构演进从 IoTDB MySQL 到去 DWD 层简化实践在钢铁行业全流程数据采集项目中我们经历了一次彻底的数据架构重构。本文分享从传统 ODS/DWD/D1 分层到去 DWD 简化为 ODS 直查 IoTDB 的实战经验。一、项目背景钢铁全流程数据采集的挑战1.1 业务场景轧钢生产线包含7 大工艺入炉、加热、锻坯、压延、锯切、收集、出坑。每支钢坯从入炉到收集需要经历数小时期间产生大量设备采集数据秒级传感器数据温度℃、压力bar、速度m/s、辊缝mm工艺步骤数据开始/结束时间、操作员、设备状态质量检测数据坯料编号、炉号、支号、规格1.2 技术栈选型层级技术选型存储内容时序数据源IoTDB 1.3.x秒级设备采集数据关系型数据库MySQL 8.0业务数据、工艺步骤、质量记录ETL 工具RestCloud / NiFi数据抽取、转换、加载后端服务Java Spring Boot业务接口、大屏展示前端Vue 3 Element Plus大屏、管理页面二、初始架构ODS/DWD 双层存储2.1 架构图IoTDB (时序原始数据) │ │ ETL 30s 聚合 ▼ MySQL ODS 层 (原始数据快照) │ │ 清洗、关联、归一化 ▼ MySQL DWD 层 (明细数据仓库) │ │ 业务查询 ▼ Java 后端 → 前端大屏2.2 存在的问题经过 6 个月的生产运行我们发现这套架构存在4 个致命问题问题1查询链路过长前端请求 → DWD → ODS → IoTDB三层跳转平均查询响应时间1.2s → 3.8s大屏 5 秒刷新一次频繁超时问题2数据一致性差ODS 和 DWD 同步依赖定时调度存在30s~2min延迟工艺状态查询时ODS 已更新但 DWD 未同步导致状态显示异常问题3维护成本高ODS 保留 90 天DWD 保留 2 年双份存储成本高需求变更时需同时修改 ODS 抽取 SQL 和 DWD 查询逻辑调度任务复杂排查问题需跨 3 个数据层问题4DWD 价值低DWD 层的清洗逻辑去重、补全、归一化在业务侧重复实现前端大屏需要原始 ODS 数据DWD 层反而成为中间商三、架构重构去 DWD 直查 IoTDB3.1 新架构设计IoTDB (时序原始数据永久存储) │ │ ETL 30s 聚合仅 ODS 需要 ▼ MySQL ODS 层 (原始数据快照保留90天) │ │ 直接查询 IoTDB 兜底 ▼ Java 后端 → 前端大屏核心原则ODS 是唯一关系型数据源保留最近 90 天热数据IoTDB 是长期存储ODS 无数据时自动回退查询DWD 层彻底下线关闭相关调度任务3.2 查询策略// 伪代码ODS IoTDB 混合查询publicListProcessDataqueryProcessData(LongstartTime,LongendTime){// 1. 先查 ODS 条数longodsCountodsMapper.countByTime(startTime,endTime);// 2. 查询绑定关系条数业务表longbindCountbindMapper.countByTime(startTime,endTime);// 3. 一致则返回 ODS不一致回退 IoTDBif(odsCountbindCount){returnodsMapper.queryByTime(startTime,endTime);}else{returniotdbClient.queryByTime(startTime,endTime);}}关键逻辑ODS 查询速度 100msIoTDB 查询速度200ms ~ 800ms取决于时间范围通过条数对比决定数据源避免不一致四、核心技术改造4.1 ODS 分区表改造-- 改造前普通表CREATETABLEfp_step_process(idBIGINTPRIMARYKEY,stove_noVARCHAR(64),create_timeDATETIME);-- 改造后按 create_time 分区预建 3 年分区CREATETABLEfp_step_process(idBIGINTPRIMARYKEY,stove_noVARCHAR(64),create_timeDATETIMENOTNULL,...)PARTITIONBYRANGE(TO_DAYS(create_time))(PARTITIONp2026_q1VALUESLESS THAN(TO_DAYS(2026-04-01)),PARTITIONp2026_q2VALUESLESS THAN(TO_DAYS(2026-07-01)),PARTITIONp2026_q3VALUESLESS THAN(TO_DAYS(2026-10-01)),...);收益查询自动剪枝90 天内数据查询速度提升3 倍历史分区可单独删除如删除 2024 年数据DROP PARTITION p2024_*4.2 IoTDB 查询兜底// IoTDB 查询模板按时间范围 设备编号StringsqlSELECT * FROM root.steel.rolling.* WHERE time {startTime} AND time {endTime} AND device {deviceNo} ORDER BY time DESC LIMIT 1000;优化点IoTDB 查询始终带时间范围避免全表扫描大屏最新一条数据ORDER BY time DESC LIMIT 1耗时 50ms分页查询避免OFFSET改用时间戳游标4.3 ETL 抽取优化# RestCloud ETL 配置优化# 1. 增量抽取基于 IoTDB 最新时间戳SELECT*FROM process_data WHERE time${last_sync_time}# 2. 批量写入JDBC Batchbatch_size5000jdbc_urljdbc:mysql://prod:3306/ods?rewriteBatchedStatementstrue# 3. 异常重试retry_count3retry_delay5s关键优化从全表同步改为增量同步ETL 耗时从 5 分钟降到30 秒开启 MySQL 批量写入重写写入性能提升10 倍五、上线效果对比5.1 性能指标指标重构前重构后提升大屏查询响应3.8s800ms4.75xETL 同步耗时5min30s10x调度任务数12 个4 个-67%存储成本2TB800GB-60%5.2 稳定性提升查询链路简化从 3 层减少到 2 层故障点减少数据一致性通过条数对比策略不一致率从5% 降到 0.1%排障效率不再需要跨 ODS/DWD 两层排查定位时间从2h 降到 20min六、踩坑实录坑1IoTDB 时间戳对齐失败现象ODS 条数 100但 IoTDB 查询返回 98 条。根因IoTDB 和 MySQL 服务器时间不同步相差 2 秒导致边缘数据落在不同分区。解决方案ETL 抽取时以IoTDB 时间戳为准MySQL 只做映射增加 2 秒缓冲窗口WHERE time start - 2s AND time end 2s坑2DWD 关闭后遗留任务干扰现象去 DWD 后某些历史报表仍然报 DWD 表不存在。根因未彻底清理 Quartz 调度任务凌晨仍触发 DWD 同步 Job。解决方案-- 查询遗留任务SELECT*FROMqrtz_triggersWHEREjob_groupLIKE%dwd%;-- 暂停并删除DELETEFROMqrtz_triggersWHEREjob_namedwd_sync_job;坑3大屏分页 vs 最新一条不一致现象大屏列表页第 1 条数据 ≠ 大屏最新一条卡片数据。根因列表页查 ODS分页最新一条查 IoTDB兜底两个数据源时间窗口不一致。解决方案统一数据源列表页和详情页都走 ODS IoTDB 混合查询增加数据源一致性校验odsCount bindCount才用 ODS七、适用场景与边界7.1 适合去 DWD 的场景✅ 业务查询以时间范围为主如最近 7 天工艺数据✅ 原始数据粒度细秒级DWD 聚合价值低✅ 团队运维能力强能handle 混合查询复杂度7.2 不建议去 DWD 的场景❌ 业务查询以维度关联为主如按产品型号汇总❌ 数据需要多源关联清洗如IoTDB ERP MES❌ 团队规模小希望查询逻辑尽量简单八、总结这次架构重构的核心收获分层不是越多越好DWD 层在特定场景下确实多余直查 ODS IoTDB 更高效混合查询策略是关键通过条数对比实现数据源自动切换兼顾性能和一致性分区表是基础ODS 按时间分区后查询性能和可维护性大幅提升渐进式重构先保留 DWD 双跑验证新查询无误后再下线降低风险如果你也在做工业数据中台或时序数据架构选型欢迎交流讨论。