工业时序数据中台架构演进:从 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 个数据层
问题4:DWD 价值低
- DWD 层的清洗逻辑(去重、补全、归一化)在业务侧重复实现
- 前端大屏需要原始 ODS 数据,DWD 层反而成为"中间商"
三、架构重构:去 DWD 直查 IoTDB
3.1 新架构设计
IoTDB (时序原始数据,永久存储) │ │ ETL 30s 聚合(仅 ODS 需要) ▼ MySQL ODS 层 (原始数据快照,保留90天) │ │ 直接查询 + IoTDB 兜底 ▼ Java 后端 → 前端大屏核心原则:
- ODS 是唯一关系型数据源,保留最近 90 天热数据
- IoTDB 是长期存储,ODS 无数据时自动回退查询
- DWD 层彻底下线,关闭相关调度任务
3.2 查询策略
// 伪代码:ODS + IoTDB 混合查询publicList<ProcessData>queryProcessData(LongstartTime,LongendTime){// 1. 先查 ODS 条数longodsCount=odsMapper.countByTime(startTime,endTime);// 2. 查询绑定关系条数(业务表)longbindCount=bindMapper.countByTime(startTime,endTime);// 3. 一致则返回 ODS,不一致回退 IoTDBif(odsCount==bindCount){returnodsMapper.queryByTime(startTime,endTime);}else{returniotdbClient.queryByTime(startTime,endTime);}}关键逻辑:
- ODS 查询速度:< 100ms
- IoTDB 查询速度: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 查询模板(按时间范围 + 设备编号)Stringsql="SELECT * 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 Batch)batch_size=5000jdbc_url="jdbc:mysql://prod:3306/ods?rewriteBatchedStatements=true"# 3. 异常重试retry_count=3retry_delay=5s关键优化:
- 从全表同步改为增量同步,ETL 耗时从 5 分钟降到30 秒
- 开启 MySQL 批量写入重写,写入性能提升10 倍
五、上线效果对比
5.1 性能指标
| 指标 | 重构前 | 重构后 | 提升 |
|---|---|---|---|
| 大屏查询响应 | 3.8s | 800ms | 4.75x |
| ETL 同步耗时 | 5min | 30s | 10x |
| 调度任务数 | 12 个 | 4 个 | -67% |
| 存储成本 | 2TB | 800GB | -60% |
5.2 稳定性提升
- 查询链路简化:从 3 层减少到 2 层,故障点减少
- 数据一致性:通过"条数对比"策略,不一致率从5% 降到 0.1%
- 排障效率:不再需要跨 ODS/DWD 两层排查,定位时间从2h 降到 20min
六、踩坑实录
坑1:IoTDB 时间戳对齐失败
现象:ODS 条数 = 100,但 IoTDB 查询返回 98 条。
根因:IoTDB 和 MySQL 服务器时间不同步(相差 2 秒),导致边缘数据落在不同分区。
解决方案:
- ETL 抽取时以IoTDB 时间戳为准,MySQL 只做映射
- 增加 2 秒缓冲窗口:
WHERE time >= start - 2s AND time <= end + 2s
坑2:DWD 关闭后遗留任务干扰
现象:去 DWD 后,某些历史报表仍然报 DWD 表不存在。
根因:未彻底清理 Quartz 调度任务,凌晨仍触发 DWD 同步 Job。
解决方案:
-- 查询遗留任务SELECT*FROMqrtz_triggersWHEREjob_groupLIKE'%dwd%';-- 暂停并删除DELETEFROMqrtz_triggersWHEREjob_name='dwd_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 双跑,验证新查询无误后再下线,降低风险
如果你也在做工业数据中台或时序数据架构选型,欢迎交流讨论。