4、数据存储高级:时序数据库(InfluxDB)简介、与HMI的集成方式
4.1 为什么HMI需要时序数据库?
做HMI开发这么多年,我见过太多项目栽在数据存储上。
早期用关系型数据库(比如MySQL、SQL Server)存历史数据,结果呢?
一个产线跑24小时,温度、压力、流量每秒采集一次。一周下来,单张表几百万行数据。查询最近一小时的趋势图,要等十几秒。更别说做报表了,导出一次能把CPU干到100%。
为什么会这样?
因为关系型数据库天生不适合处理时序数据。它擅长的是事务处理,比如订单、用户信息。而工业现场的数据,说白了就是「时间戳 + 数值」的序列。这种数据有几个特点:
- 写入频繁:每秒成百上千个点
- 很少更新:历史数据写进去基本不改
- 按时间查询:查最近一小时、昨天、上周
- 数据量大:一年下来轻松上亿条
关系型数据库的B+树索引,在这种场景下效率极低。我有个项目,客户要求保存3年的数据,用SQL Server存了两个月就扛不住了,查询慢得像蜗牛。
后来换了时序数据库,问题迎刃而解。
核心结论:工业HMI的历史数据存储,时序数据库是更优解。不是关系型数据库不能用,而是用起来成本高、性能差。
4.2 InfluxDB是什么?
InfluxDB是目前最流行的开源时序数据库之一。它专门为处理时间序列数据而设计。
我个人习惯把它比作「工业数据的黑匣子」。你只管往里写数据,它自动按时间索引、压缩、保留。查询时,按时间范围一拉,数据就出来了。
它的核心概念其实不多,就几个:
| 概念 | 类比关系型数据库 | 说明 |
|---|---|---|
| Measurement | 表(Table) | 一类数据的集合,比如「温度数据」、「压力数据」 |
| Tag | 索引列 | 用于过滤和分组,比如设备ID、产线编号 |
| Field | 数据列 | 实际存储的数值,比如温度值、压力值 |
| Timestamp | 主键 | 时间戳,精确到纳秒 |
| Retention Policy | 无 | 数据保留策略,自动删除过期数据 |
举个例子,你有一条数据:
temperature,device_id=PLC01,line=A value=25.6 1695000000000000000
这条数据的意思是:
- Measurement是temperature
- Tag是device_id=PLC01, line=A
- Field是value=25.6
- Timestamp是1695000000000000000(纳秒时间戳)
嗯,这里要注意:Tag和Field的区别很重要。Tag会被索引,适合做查询条件。Field不会被索引,适合存数值。如果你把设备ID放在Field里,查询时会慢很多。
我的经验:把经常用来过滤的字段设为Tag,比如设备ID、产线编号。把数值类的字段设为Field。这个设计决策会影响查询性能,建议一开始就规划好。
4.3 InfluxDB与HMI的集成方式
集成方式主要有三种。我按推荐程度排序:
方式一:通过REST API直接写入
这是最灵活的方式。HMI端(比如用C#、Python)直接调用InfluxDB的HTTP API写入数据。
代码示例(Python):
import requests
import json
from datetime import datetime
# InfluxDB API地址
url = "http://192.168.1.100:8086/write?db=mydb"
# 构造数据行
data = "temperature,device_id=PLC01,line=A value=25.6 " + str(int(datetime.now().timestamp() * 1e9))
# 发送请求
response = requests.post(url, data=data)
if response.status_code == 204:
print("写入成功")
else:
print("写入失败:", response.text)
这种方式的好处是:
- 不依赖中间件,HMI直接写
- 控制粒度细,可以自定义写入频率
- 适合数据量大的场景
缺点呢?HMI端需要处理网络异常、重试逻辑。我曾经在一个项目中,HMI每100ms写一次数据,网络偶尔抖动导致数据丢失。后来加了本地缓存和重试机制才解决。
避坑指南:我曾经遇到一个坑——HMI写入频率太高,InfluxDB扛不住。后来发现是写入时每条数据单独发一个HTTP请求。正确的做法是批量写入,一次发几十条甚至几百条数据。InfluxDB对批量写入的优化非常好。
方式二:通过中间件(如Telegraf)采集
Telegraf是InfluxData公司推出的数据采集代理。它支持从各种数据源采集数据,然后写入InfluxDB。
配置示例(telegraf.conf):
[[inputs.mqtt_consumer]]
servers = ["tcp://192.168.1.200:1883"]
topics = ["factory/+/temperature"]
data_format = "json"
[[outputs.influxdb]]
urls = ["http://192.168.1.100:8086"]
database = "mydb"
这个配置的意思是:从MQTT订阅主题,收到数据后自动写入InfluxDB。
这种方式的好处:
- HMI端不需要写数据存储代码
- Telegraf自带重试、缓冲、数据转换功能
- 适合已有MQTT等中间件的系统
缺点就是多了一层依赖,架构复杂了一点。
方式三:通过HMI自带的数据库接口
有些高端HMI(比如西门子WinCC、施耐德Vijeo Designer)内置了时序数据库接口。可以直接配置数据源为InfluxDB。
以WinCC为例,配置步骤大致是:
- 在WinCC中安装InfluxDB驱动
- 配置数据源连接字符串
- 选择需要归档的变量
- 设置归档周期(比如每秒、每分钟)
这种方式最省事,但灵活性最低。你想想看,如果HMI不支持InfluxDB,那就没办法了。
4.4 实际项目中的选型建议
说了这么多,到底怎么选?我给出一个简单的判断标准:
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 数据量小(<100点/秒) | 方式三(HMI内置接口) | 配置简单,维护成本低 |
| 数据量中等(100-1000点/秒) | 方式一(REST API) | 灵活可控,性能足够 |
| 数据量大(>1000点/秒) | 方式二(Telegraf) | 解耦HMI,专业采集更稳定 |
| 已有MQTT等中间件 | 方式二(Telegraf) | 复用现有架构,减少改动 |
我个人习惯是:新项目优先考虑方式一。因为HMI直接写InfluxDB,架构简单,出了问题好排查。如果后期数据量上来了,再考虑引入Telegraf做缓冲。
4.5 一个完整的集成示例
最后,我给出一个完整的示例。假设你有一个PLC,采集温度数据,通过OPC UA读到HMI,然后写入InfluxDB。
HMI端(C#)的代码骨架:
// 1. 从OPC UA读取数据
float temperature = ReadFromOPCUA("ns=2;s=PLC1.Temperature");
// 2. 构造InfluxDB数据点
var dataPoint = $"temperature,device_id=PLC01 value={temperature} {DateTimeOffset.UtcNow.ToUnixTimeNanoseconds()}";
// 3. 批量缓存(每10条或每5秒写入一次)
cache.Add(dataPoint);
if (cache.Count >= 10 || cacheTimer.Elapsed > TimeSpan.FromSeconds(5))
{
await WriteToInfluxDB(cache);
cache.Clear();
}
// 4. 写入InfluxDB
async Task WriteToInfluxDB(List<string> points)
{
using var client = new HttpClient();
var content = new StringContent(string.Join("\n", points));
var response = await client.PostAsync("http://192.168.1.100:8086/write?db=mydb", content);
// 检查响应...
}
这个示例里,我做了两件事:
- 批量写入,避免频繁HTTP请求
- 定时写入,防止数据积压
嗯,这里要注意:时间戳一定要用UTC时间,不要用本地时间。否则跨时区或者夏令时切换时,数据会乱。我吃过这个亏,后来所有项目都强制用UTC。
总结一下:InfluxDB是工业HMI历史数据存储的好帮手。集成方式有三种,选哪种取决于你的场景。核心原则是:批量写入、使用UTC时间、合理设计Tag和Field。做到这三点,数据存储这块基本稳了。