2、核心架构设计:分层架构(源、湖、仓、应用)、技术选型(Hadoop/Spark/Flink/ClickHouse)、数据流转设计
好,咱们直接切入正题。储能数据中台的核心架构,说白了就是解决一个问题:数据从哪来、怎么存、怎么算、怎么用。我见过不少团队,一上来就堆技术栈,结果数据链路跑不通,运维成本还高得吓人。今天我就把我在几个储能项目里踩过的坑、总结出的经验,掰开揉碎了讲给你听。
2.1 分层架构:源、湖、仓、应用
我个人习惯把储能数据中台分成四层。你想想看,储能系统的数据源特别杂——BMS(电池管理系统)、PCS(储能变流器)、EMS(能量管理系统)、环境传感器,甚至还有第三方气象数据。如果不分层,数据治理就是一场噩梦。
这四层分别是:
- 源(Source Layer):数据采集层。负责对接各类储能设备、SCADA系统、交易平台。我建议这里只做轻量级清洗,比如去重、格式统一,别做复杂计算。
- 湖(Data Lake):原始数据存储层。所有数据原封不动扔进湖里,保留最细粒度。我在项目中遇到过,有些同事为了省存储空间,直接对原始数据做聚合,结果后来需要回溯故障时,数据已经丢了细节,追悔莫及。
- 仓(Data Warehouse):数据仓库层。这里才是真正的业务建模。按储能场景划分主题域,比如电池健康度、充放电效率、收益分析。数据要经过清洗、转换、关联,形成标准化的宽表。
- 应用(Application Layer):数据服务层。对外提供API、报表、大屏、告警。这一层要轻,只做查询和展示,不做复杂计算。
核心原则:每一层只做自己该做的事。源层别做聚合,湖层别做建模,仓层别做实时,应用层别做ETL。各层职责清晰,出了问题才好定位。
下面这张图是我手绘的分层架构逻辑,你可以直观感受一下数据是怎么流动的:
2.2 技术选型:Hadoop/Spark/Flink/ClickHouse
技术选型这块,我踩过的坑最多。很多团队喜欢追新,什么热就用什么。但储能场景有它自己的特点:数据量大(一个百兆瓦时电站每天产生几亿条时序数据)、实时性要求高(秒级告警)、查询模式固定(按设备、按时间窗口聚合)。
我现在的推荐组合是这样的:
| 组件 | 用途 | 为什么选它 | 避坑指南 |
|---|---|---|---|
| Hadoop HDFS | 数据湖底层存储 | 成熟稳定,支持PB级存储,成本低 | 别用默认副本数(3),储能数据冷热分明,冷数据可以降到2副本 |
| Apache Spark | 离线批处理、ETL | 内存计算快,适合复杂转换 | 注意shuffle调优,我曾经因为没调spark.sql.shuffle.partitions,一个ETL跑了6小时 |
| Apache Flink | 实时流处理 | 真正的流式计算,毫秒级延迟 | 状态后端用RocksDB,别用内存,储能数据量大,内存撑不住 |
| ClickHouse | OLAP查询、大屏、报表 | 列式存储,聚合查询极快 | 避免大表JOIN,ClickHouse的JOIN性能一般,我习惯用物化视图预聚合 |
我的个人经验:如果你团队不大,别自己搭Hadoop集群,直接用云上的EMR或Databricks。运维成本真的很高,我曾经有一个月光修HDFS的NameNode就花了三天。
2.3 数据流转设计
数据怎么从设备端流到应用层?这里我画了一条典型链路,你感受一下:
- 设备端采集:BMS每100ms上报一次电压、电流、温度数据,通过MQTT或Kafka接入。
- 实时流处理:Flink消费Kafka,做两件事——实时告警(比如温度超过阈值立即触发)和实时聚合(比如每分钟计算一次平均电压)。
- 数据落地:Flink将原始数据写入HDFS(数据湖),将聚合结果写入ClickHouse(数据仓库)。
- 离线ETL:每天凌晨,Spark读取HDFS上的原始数据,做深度清洗、关联、建模,生成宽表,再写入ClickHouse。
- 数据服务:应用层通过ClickHouse的HTTP接口查询数据,支撑大屏、报表、API。
嗯,这里要注意一个关键点:实时和离线两条链路的数据口径必须一致。我曾经在项目里遇到过,实时聚合的“当日充电量”和离线计算的差了5%,排查了两天才发现是Flink和Spark的时间窗口定义不一致。后来我统一用事件时间(Event Time),才彻底解决。
避坑指南:我曾经在数据流转中忽略了“数据回溯”场景。比如设备断网了2小时,恢复后历史数据补传,这时候Flink的Watermark机制要处理好,否则乱序数据会导致聚合结果出错。建议用Flink的allowedLateness和side output处理迟到数据。
下面是一个简化的Flink实时处理代码片段,用于展示数据流转的核心逻辑:
// Flink 实时聚合储能设备数据
DataStream<SensorData> sourceStream = env
.addSource(new FlinkKafkaConsumer<>("bms_topic", new JSONDeserializationSchema(), props))
.assignTimestampsAndWatermarks(
WatermarkStrategy.<SensorData>forBoundedOutOfOrderness(Duration.ofSeconds(10))
.withTimestampAssigner((event, timestamp) -> event.getEventTime())
);
// 按设备ID和1分钟窗口聚合
DataStream<AggregatedResult> aggregatedStream = sourceStream
.keyBy(SensorData::getDeviceId)
.window(TumblingEventTimeWindows.of(Time.minutes(1)))
.aggregate(new AvgVoltageAggregator())
.name("bms-1min-aggregation");
// 结果写入ClickHouse
aggregatedStream.addSink(new ClickHouseSink("jdbc:clickhouse://localhost:8123/energy", "agg_bms_1min"));
你看,代码其实不复杂。但真正难的是数据一致性保障和异常处理。比如Flink任务挂了重启,怎么保证不丢数据、不重复计算?我建议开启Flink的Checkpoint,并设置exactly-once语义。ClickHouse写入时用幂等性设计,避免重复数据。
最后总结一下核心架构设计的三个要点:
- 分层要清晰:源、湖、仓、应用各司其职,别混在一起。
- 选型要务实:HDFS存原始数据,Spark做离线,Flink做实时,ClickHouse做查询。别盲目追新。
- 流转要可控:实时和离线口径统一,异常处理机制完善,数据可回溯。
好了,这一章就到这里。下一章我们聊聊数据治理——怎么保证储能数据的质量,怎么管理元数据。到时候我会分享一个我亲手搭建的数据质量监控体系,保证让你少走弯路。