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为例,配置步骤大致是:

  1. 在WinCC中安装InfluxDB驱动
  2. 配置数据源连接字符串
  3. 选择需要归档的变量
  4. 设置归档周期(比如每秒、每分钟)

这种方式最省事,但灵活性最低。你想想看,如果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。做到这三点,数据存储这块基本稳了。