2、核心架构设计:分层架构(源、湖、仓、应用)、技术选型(Hadoop/Spark/Flink/ClickHouse)、数据流转设计

好,咱们直接切入正题。储能数据中台的核心架构,说白了就是解决一个问题:数据从哪来、怎么存、怎么算、怎么用。我见过不少团队,一上来就堆技术栈,结果数据链路跑不通,运维成本还高得吓人。今天我就把我在几个储能项目里踩过的坑、总结出的经验,掰开揉碎了讲给你听。

2.1 分层架构:源、湖、仓、应用

我个人习惯把储能数据中台分成四层。你想想看,储能系统的数据源特别杂——BMS(电池管理系统)、PCS(储能变流器)、EMS(能量管理系统)、环境传感器,甚至还有第三方气象数据。如果不分层,数据治理就是一场噩梦。

这四层分别是:

  • 源(Source Layer):数据采集层。负责对接各类储能设备、SCADA系统、交易平台。我建议这里只做轻量级清洗,比如去重、格式统一,别做复杂计算。
  • 湖(Data Lake):原始数据存储层。所有数据原封不动扔进湖里,保留最细粒度。我在项目中遇到过,有些同事为了省存储空间,直接对原始数据做聚合,结果后来需要回溯故障时,数据已经丢了细节,追悔莫及。
  • 仓(Data Warehouse):数据仓库层。这里才是真正的业务建模。按储能场景划分主题域,比如电池健康度、充放电效率、收益分析。数据要经过清洗、转换、关联,形成标准化的宽表。
  • 应用(Application Layer):数据服务层。对外提供API、报表、大屏、告警。这一层要轻,只做查询和展示,不做复杂计算。

核心原则:每一层只做自己该做的事。源层别做聚合,湖层别做建模,仓层别做实时,应用层别做ETL。各层职责清晰,出了问题才好定位。

下面这张图是我手绘的分层架构逻辑,你可以直观感受一下数据是怎么流动的:

源层(Source Layer) BMS数据 | PCS数据 | EMS数据 | 环境传感器 | 交易数据 湖层(Data Lake) 原始数据存储(Parquet/ORC) | 全量历史 | 不可变 仓层(Data Warehouse) 主题域建模 | 宽表 | 指标计算 | 数据质量校验 应用层(Application Layer) 数据湖:保留原始细节 数据仓库:业务建模

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 数据流转设计

数据怎么从设备端流到应用层?这里我画了一条典型链路,你感受一下:

  1. 设备端采集:BMS每100ms上报一次电压、电流、温度数据,通过MQTT或Kafka接入。
  2. 实时流处理:Flink消费Kafka,做两件事——实时告警(比如温度超过阈值立即触发)和实时聚合(比如每分钟计算一次平均电压)。
  3. 数据落地:Flink将原始数据写入HDFS(数据湖),将聚合结果写入ClickHouse(数据仓库)。
  4. 离线ETL:每天凌晨,Spark读取HDFS上的原始数据,做深度清洗、关联、建模,生成宽表,再写入ClickHouse。
  5. 数据服务:应用层通过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做查询。别盲目追新。
  • 流转要可控:实时和离线口径统一,异常处理机制完善,数据可回溯。

好了,这一章就到这里。下一章我们聊聊数据治理——怎么保证储能数据的质量,怎么管理元数据。到时候我会分享一个我亲手搭建的数据质量监控体系,保证让你少走弯路。


蓝海数据掘金营,专注资料整理