4. 数据存储与建模:数据湖与数据仓库的协同之道
聊到数据存储与建模,我脑子里第一个蹦出来的词就是「分层」。说白了,数据不能一股脑儿往里塞,得有规矩。我在好几个储能项目里都吃过「不分层」的亏——查询慢、口径乱、运维想骂人。后来老老实实按数据湖+数据仓库分层来搞,才算是把数据治理这摊子事理顺了。
核心思路:数据湖存原始数据,数据仓库管分析数据。两者互补,不是替代关系。
4.1 数据湖:HDFS 与对象存储的选择
数据湖,说白了就是个大池子。原始数据、日志、传感器报文、设备状态快照……统统往里丢。我习惯把数据湖当作「数据保险箱」——先存下来再说,后面怎么用再慢慢想。
在储能场景里,数据湖主要存两类东西:
- 设备原始报文:BMS、PCS、EMS 每秒上报的 JSON 或二进制数据
- 历史快照:每日全量导出的设备配置、告警记录、运维日志
选 HDFS 还是对象存储?我的经验是:
| 对比维度 | HDFS | 对象存储(如 MinIO、OSS) |
|---|---|---|
| 适用场景 | 批处理、大文件顺序读写 | 小文件多、随机访问、冷热数据分层 |
| 扩展性 | NameNode 有瓶颈,需运维 | 天然分布式,扩展简单 |
| 成本 | 较高(需多副本) | 较低(纠删码) |
| 我个人的选择 | 实时流处理、需要计算亲和性时 | 长期归档、跨区域复制时 |
我的建议:如果团队运维能力一般,优先用对象存储。HDFS 虽然性能好,但 NameNode 挂了真的很要命。我曾经在凌晨三点被叫起来修 NameNode……嗯,那滋味不好受。
4.2 数据仓库分层:ODS → DWD → DWS → ADS
数据仓库分层,我把它理解成「厨房流水线」。原材料(ODS)→ 清洗切配(DWD)→ 半成品加工(DWS)→ 成品上桌(ADS)。每一层都有明确的职责,不能越级。
来看看每层具体干什么:
ODS(操作数据存储)
这一层就是数据湖的「镜像」。数据从湖里直接拉过来,几乎不做任何转换。我见过很多团队在 ODS 层就开始做清洗,这是大忌。ODS 只做两件事:保留原始字段、记录数据时间戳。
-- ODS 层建表示例(储能设备状态表)
CREATE TABLE ods_device_status (
device_id STRING,
status_code INT,
voltage DOUBLE,
current DOUBLE,
soc DOUBLE,
report_time TIMESTAMP,
etl_time TIMESTAMP -- 记录抽取时间
) PARTITIONED BY (dt STRING);
DWD(明细数据层)
这一层开始做「脏活累活」。数据清洗、去重、格式统一、维度退化。我习惯把 DWD 叫作「干净明细层」——所有业务口径在这里统一,下游直接用。
举个例子:ODS 层里 soc 字段可能是 0-100 的整数,也可能是 0.0-1.0 的小数。DWD 层必须统一成 0.00-100.00 的浮点数,并标记异常值。
避坑指南:我曾经在 DWD 层漏掉了时区转换,导致某储能站凌晨 2 点的数据被归到前一天。排查了整整两天才发现是 UTC+8 没处理好。所以,时间字段一定要显式声明时区。
DWS(汇总数据层)
这一层做轻量聚合。比如:每 5 分钟的设备平均电压、每小时的总充放电量、每日的 SOC 极值。DWS 的数据量比 DWD 小很多,查询也快得多。
-- DWS 层:每 5 分钟聚合一次设备状态
CREATE TABLE dws_device_5min_agg (
device_id STRING,
window_start TIMESTAMP,
avg_voltage DOUBLE,
avg_current DOUBLE,
max_soc DOUBLE,
min_soc DOUBLE,
record_count INT
) PARTITIONED BY (dt STRING);
ADS(应用数据层)
这一层直接面向业务。报表、大屏、API 接口的数据都从这里取。ADS 的表结构通常很「宽」,一个表里可能有几十个字段,就是为了减少 JOIN。
记住:ADS 层不要做复杂计算。所有计算逻辑应该在 DWD 或 DWS 层完成。ADS 只做「查」和「展示」。
4.3 维度建模与宽表设计
维度建模,说白了就是「星型模型」和「雪花模型」的选择。在储能行业,我强烈推荐星型模型。为什么?因为储能业务的维度相对固定——时间、设备、电站、告警类型。用星型模型,查询路径短,理解成本低。
但实际工作中,我更常用的是宽表。你想想看,一个储能分析报表,可能要同时展示设备名称、电站区域、SOC、电压、温度、告警级别……如果按星型模型做,得 JOIN 五六张表。查询慢不说,维护也麻烦。
宽表的设计原则:
- 高频字段冗余进来:比如设备名称、电站名称,不要每次都去查维度表
- 低频字段保持关联:比如设备固件版本、运维人员信息,一年查不了几次,留着 JOIN 就行
- 注意数据一致性:冗余字段更新时要同步,否则会出现「同一个设备两个名称」的尴尬
-- 宽表示例:储能设备实时状态宽表
CREATE TABLE ads_device_realtime_wide (
device_id STRING,
device_name STRING, -- 冗余自维度表
station_id STRING,
station_name STRING, -- 冗余自维度表
region STRING, -- 冗余自维度表
voltage DOUBLE,
current DOUBLE,
soc DOUBLE,
temperature DOUBLE,
alarm_level INT,
alarm_desc STRING,
report_time TIMESTAMP,
etl_time TIMESTAMP
) PARTITIONED BY (dt STRING);
我的经验:宽表不是越宽越好。我见过一张表 200 个字段的「超级宽表」,查询确实快,但维护成本极高。建议控制在 30-50 个字段以内,超过这个数,就该考虑拆分了。
4.4 知识体系总览
下面这张图,是我对本章知识结构的总结。你可以把它当作「数据存储与建模」的导航图:
嗯,这张图基本把本章的核心逻辑串起来了。数据湖是地基,数据仓库分层是骨架,维度建模和宽表设计是血肉。三者缺一不可。
最后说一句:建模没有银弹。我在不同储能项目里用过完全不同的模型设计——有的项目适合星型,有的适合宽表。关键是要理解业务查询模式,然后「对症下药」。别为了建模而建模,数据最终是要给人用的。