数据存储与处理:时序数据库(InfluxDB)基础、数据清洗与ETL流程、数据湖架构设计

好,咱们进入第三章。说实话,这一章是整套工具链的「腰杆子」。数据存不好、洗不干净,后面所有分析都是空中楼阁。我个人习惯,做能源系统第一步不是写算法,而是先把数据链路理清楚。

你想想看,一个光伏电站,每秒钟可能产生上千条数据点——电压、电流、功率、温度、辐照度。这些数据有什么特点?时间密集、写入频繁、很少修改。传统的关系型数据库?嗯,不是不能用,但成本高、查询慢。这时候,时序数据库就派上用场了。

时序数据库(InfluxDB)基础

InfluxDB 是我在项目中用得最多的时序数据库。为什么?因为它专为这类场景设计。说白了,它就是为「带时间戳的数值」量身定做的仓库。

核心概念其实就三个:

  • Measurement:相当于关系库里的表。比如我们存「逆变器数据」,measurement 就叫 inverter_data。
  • Tag:标签,用于索引和分组。比如 site_id(电站编号)、device_id(设备编号)。tag 会被自动索引,查询飞快。
  • Field:实际数值。比如 power(功率)、voltage(电压)。field 不会被索引,但存储的是真实数据。

举个例子,一条典型的数据写入:

inverter_data,site_id=S001,device_id=INV-01 power=320.5,voltage=380.2,temperature=45.3 1695000000

这条数据什么意思?电站 S001 的 INV-01 号逆变器,在时间戳 1695000000 时,功率 320.5kW,电压 380.2V,温度 45.3°C。

我的小技巧: tag 不要放太多唯一值。比如设备编号可以放 tag,但具体数值千万别放 tag。我在一个项目中见过有人把「功率值」也放 tag 里,结果索引爆炸,查询慢得像蜗牛。

查询语法也简单。比如查最近一小时所有逆变器的平均功率:

SELECT mean(power) FROM inverter_data 
WHERE time > now() - 1h 
GROUP BY device_id

嗯,这里要注意:InfluxDB 的 SQL 风格叫 InfluxQL,跟标准 SQL 有点区别。但上手很快,半天就能熟练。

数据清洗与ETL流程

数据存进去了,但问题来了——原始数据往往「脏」得不行。我曾经接手过一个风电项目,传感器时不时跳变,风速数据里居然出现了 -999 这种值。你说这能直接用吗?

所以,ETL(Extract, Transform, Load)是必须的。我个人习惯把 ETL 分成三步:

  1. 提取(Extract):从源头拉数据。可能是 MQTT 消息队列,也可能是直接读设备文件。
  2. 转换(Transform):清洗、补全、聚合。这是核心。
  3. 加载(Load):写入目标存储,比如 InfluxDB 或数据湖。

清洗时常见的问题有哪些?我列个表:

问题类型 示例 处理方式
缺失值 某时刻功率为空 线性插值或用前一时刻值填充
异常值 温度显示 999°C 设定合理阈值,超出则剔除或标记
重复数据 同一时间戳出现两条相同记录 去重,保留第一条或最后一条
时间戳乱序 数据到达顺序与时间顺序不一致 按时间戳排序后写入

我一般用 Python 写 ETL 脚本。核心逻辑大概这样:

def clean_data(raw_df):
    # 1. 去重
    df = raw_df.drop_duplicates(subset=['time', 'device_id'])
    
    # 2. 处理缺失值
    df = df.interpolate(method='linear')
    
    # 3. 过滤异常值
    df = df[(df['power'] >= 0) & (df['power'] <= 500)]
    
    # 4. 排序
    df = df.sort_values('time')
    
    return df
避坑指南: 我曾经犯过一个错——在清洗时直接修改原始数据。后来发现某个异常值其实是设备故障的信号,删了就丢了重要线索。现在我坚持:原始数据永远保留一份副本,清洗后的数据另存一份。

数据湖架构设计

说到数据湖,很多人觉得高大上。其实说白了,就是一个「大池子」,什么格式的数据都能往里扔。结构化、半结构化、非结构化,来者不拒。

在能源系统里,数据湖特别适合存这些:

  • 原始传感器数据(CSV、Parquet)
  • 设备日志文件(文本格式)
  • 气象数据(JSON、NetCDF)
  • 历史报表(Excel、PDF)

我设计数据湖时,一般分三层:

  1. 原始层(Raw Zone):数据原封不动存进来,格式不变。用于回溯和审计。
  2. 清洗层(Cleansed Zone):经过 ETL 处理后的干净数据。格式统一为 Parquet,按时间分区。
  3. 应用层(Application Zone):按业务需求聚合后的数据。比如按小时、按天统计的发电量。

下面这张图能帮你理解整体架构:

数据湖三层架构(能源系统) 数据源 传感器 | MQTT | 设备日志 | 气象站 | 历史报表 原始层(Raw Zone) 数据原样存储 | CSV / JSON / 原始二进制 | 用于回溯 清洗层(Cleansed Zone) ETL 处理后 | Parquet 格式 | 按时间分区 | 去重、补全、过滤 应用层(Application Zone) 聚合数据 | 小时/日/月统计 | 供 BI 报表和算法调用

你看,数据从源头进来,先落到原始层「存着不动」,然后 ETL 任务定时跑,把干净数据推到清洗层。最后根据业务需求,再聚合到应用层。每一层各司其职,互不干扰。

核心原则: 原始数据永不删除,清洗逻辑必须可重复。这样哪天发现清洗规则错了,还能重新跑一遍。

实际落地时,我常用 MinIO 或 HDFS 做底层存储,Spark 或 Flink 做 ETL 计算。但小项目用 Python + 本地文件系统也够用。关键不是技术选型多炫酷,而是架构清晰、数据可靠。

嗯,这一章内容不少。时序数据库解决了「怎么写快、怎么查快」的问题,ETL 解决了「数据脏了怎么办」的问题,数据湖解决了「数据多了怎么管」的问题。三者配合,才能支撑起一个靠谱的能源数据平台。

你想想看,如果数据链路没打通,后面做再漂亮的图表、再高级的算法,都是白搭。所以这一章,值得多花点心思。


专注资料整理