开源纯C#工控网关+组态软件(七)数据采集与归档

时间:2022-09-15 12:45:54

、   引子

在当前自动化、信息化、智能化的时代背景下,数据的作用日渐凸显。而工业发展到如今,科技含量和自动化水平均显著提高,但对数据的采集、利用才开始起步。

对工业企业而言,数据采集日益受到重视,主要应用场景包括:

  1. 节能降耗。投入(如车间的水电气能耗、设备工时、原料耗用)和产出(产量、批数)这些成本核算的关键数据通过传感器采集,取代人工抄表已成为趋势。
  2. 绩效考评。投入、产出、损耗、工时数据,其对管理者的决策支持、对员工的绩效评估都很重要。
  3. 批次追溯。食品安全形势日益严峻,对物料的追溯也成为国家硬指标。追溯就是追根溯源,批次生产的每个环节都需要数据跟踪。
  4. 设备管理。如设备的运行时长对于设备保养、故障频率对于设备维护、设备参数对于工艺优化。

数据既然如此重要,对于SCADA不但必须有,而且高要求:

  1. 准确性。信号不能失真,采集精度和时间戳尽可能精确;也不能带入太多干扰和噪音。
  2. 完整性。信号不能频繁丢失、丢步、跳步,万一信号断开,要快速重连,或者有冗余机制。
  3. 大容量。大数据,首先要能撑的起这个“大”。大项目动辄几万点,采集频率又高,一天下来数据量都惊人,日积月累更是天量。例如对于 1万点的系统, 1秒钟存储一次,每次单点占用 8字节,保存 10年的数据量将有 10000*8*10*365*86400=25228800000000字节,也就是 23TGB。若用 80GB硬盘存放,需 293块硬盘。如此庞大的数据量,还要求快速插入、快速查询。

要实现这些指标,非常具有挑战性。

二、   实时库与历史库

  • 概述

工控环境特殊性在于,大量测点快速变化,需高速存取, IO密集型;数据结构简单规则,无非就是名称/ID、值、时间戳这些;数据流式存储,只需在尾部插入,不删不改。

因此常规的关系数据库不仅存取速度跟不上,也显得杀鸡用牛刀。实时库和历史库就是为工业环境准备的,测点的实时数据存储在内存,保证最快的存取速度;数据超过一定范围需要转储入历史库,我这里用了自定义格式的二进制文件,力求数据单元空间占用最小化、同时查询速度最大化。

  • 实时库

测点数据在内存中,包含【下位机映射缓存:ICache】→【快照数据集:TagList】→【历史数据缓存:HistoryList】这样的三级结构。

ICache是下位机当前数据的缓存,随扫描过程实时更新,继承IReaderWriter接口,可读可写,可以通过Tag的Read\Write读取和更新。

对当前所有测点数据的快照查询,可以通过Tag的清单列表MetaTagList结合神器Linq实现。Linq对内存列表数据的查询能力可以说既强大又优雅,这是微软送给C#码农的礼物,不再赘述。

测点数据改变就会生成一条新的记录。这些记录如马上转储到数据库或文件,则测点数量多变化快,其IO是系统不能承担之重。但如果测点记录堆积过多不及时清理,则一方面可靠性下降,如系统崩溃、断电就会发生大量数据点丢失,同时内存占用越来越大,影响系统性能。所以测点历史数据的缓存容量应可根据测点数量和存取频率自适应或由用户自定义。

  • 历史库

海量的测点数据,普通关系数据库是难以招架的。如SQL SERVER免费版只有4-10个G上限。而这个容量可能一个月就溢出了。

因此,为了适应天量数据,就需要二进制文件存储。有人会问为啥不用NO SQL,Hadoop这些高大上的东东,我的观点是不追求高大上,因为工控数据不同于搜索引擎,都是简单而标准的结构。可以根据其特点进行有针对性的设计,无需部署复杂的NO SQL架构也一样可以实现高性能。

历史数据库要最大限度的压缩数据,同时又要保证快速插入、快速查询。

如何保证数据单元最小化?

分析存储结构,一条记录包括变量名、当前值、时间戳。

变量名可能为一个长字符串,数据量大之后显然是过于冗长。因此代之以ID号(2字节)。还可以进一步压缩,如相同变量存在一起,ID也可以省了。

当前值大部分是浮点数,4字节,这个不能缩减,否则影响数据精度。

时间戳为DateTime,要占8字节,但如果数据按日排序,日期部分省去,4个字节的时间部分就可以精确到毫秒。

这样,通过合理设计存储结构,一条记录可以压缩到8个字节(开关量5个字节)。

如何保证快速插入记录?

存档文件日积月累肯定是越来越庞大,如果采用覆盖式写入或更新写入,不仅可靠性下降,读写成本也越来越高。想快速插入必须保证每次写入不改变原来的数据,仅仅在末尾追加。

如何保证快速查询?

首先为保证可靠性和数据容量限制,数据分月存放。文件名为【年-月.bin】。如需跨月查询,按文件名搜索拼接即可。开头256个字节存放日期索引。32*8字节,对应每日记录的头指针,也即上一日记录的末尾。

主索引下,每一天的记录头为日内索引区。包含一个索引数组,每一项索引有变量ID、变量长度、数量。如要查某日某ID的变量,即可先找到ID,再根据其变量长度*数量累加计算,即可定位到该变量的第一条记录。

同一变量的记录按时间戳顺序排列。这样,要定位到该变量某一时间的记录,即可对时间戳采用二分法快速定位。

字符串类型的归档比较特殊,专门在EventLog作为日志存取。

三、   数据转储流程

  • 为什么要建立三级转储

数据在什么情况下会被采集?默认是变化了采集。如果一个数据长期不变,但需要定期采样,可以设置归档周期:

开源纯C#工控网关+组态软件(七)数据采集与归档

数据被采集之后首先是存在内存中。内存的特点是快,小。存取快,但是容量有限,采样数据堆积多了就要清理转储到关系数据库。

为什么要多一层关系数据库?因为采样的数据是时间序列的,但最终二进制文件的索引结构按照变量-时间戳排布,比如依次排入变量A-13:40,变量B-13:41,变量A-13:42这样一个时间序列,如直接写二进制存档文件就需按变量排序并重新整理写入,文件越大其写入效率越低,对系统拖累越大。而先转储到关系数据库,再定期将上一日的数据转储到二进制文件,既可以充分利用关系数据库的高性能批量插入功能(在SQL SERVER就是Sqlbulkcopy),又可利用关系库的查询排序能力,一举两得,转储之后数据库记录清空,也避免关系库容量溢出的问题。

由上所述,转储包含【历史数据缓存:HistoryList】→【关系数据库:Log_HDA】→【二进制文件:bin文件】三级。

每转储成功一次,上一级的数据就清空,保证每一级之间的数据不重叠。

这样一来,数据记录就分布于三个位置:内存、关系库、二进制文件。要查询数据,就需要对这三部分数据进行“拼接”。拼接的规则就是以当前级最末一条记录的时间戳为准。如当前级中没有,就查下一级。

  • 源头:内存数据库

内存数据库是一个HistoryData列表。有两种情况可以触发清理:定期清理、溢出清理。定期清理是设置固定的周期。溢出就是超过一定大小,到时间就转到关系库。可由server.xml配置。

  • 中介:关系数据库

就是数据库的Log_HDA表。承上(内存)启下(二进制文件),暂存数据。也包含ID、值、时间戳这几个字段。每天凌晨开始,网关服务调用DataHelper内部的WriteToFile方法(实际是调用关系库的WRITEHDATA存储过程),对暂存的测点记录按时间、变量排序,转储到二进制文件中。如写入失败,判断最后一个时间戳,下一次继续追加写入,类似断点续传。

  • 存储:二进制数据库

二进制存档文件按月存放,自带索引。所有对其操作均在DataHelper的HDAIOHelper 类中。包括从数据库写入、查询、定期转储、压缩归档(用旋转门算法)等。为提高读写性能,采用内存映射文件MemoryMappedFile。

四、   应用场景

  • 数据应用场景

数据的应用场景,主要是查询、显示、挖掘。查询→生成各种报表、图表,以供人工分析比较;显示→图形化展示,一目了然;挖掘→结合先进的挖掘工具,找出数据内在关联性,提供决策支持。

开源纯C#工控网关+组态软件(七)数据采集与归档

  • 数据查询

目前支持的查询场景包括:按时间段检索、按变量ID检索、获取某变量在一段时间内的平均值/最大值/最小值/初始值/当前值。如要对一段记录执行复杂查询(如按时间间隔分组等),需要取出该时间段内所有记录,用Linq查询。

  • 数据显示

目前支持实时数据显示和历史数据趋势图。我这里用了一套微软俄罗斯研究院的DynamicDataDisplay开源组件,性能不错,很适合动态图显示,目前还发展出了javascript版本。

开源纯C#工控网关+组态软件(七)数据采集与归档

  • 数据报表

利用微软的RDLC报表和Chart图表的强大功能,可以方便的设计出各种复杂报表、图表。顺带赞一下RDLC,集成于Visual Studio和SQL SERVER,可以在Web显示,支持内嵌表格、仪表、图表、钻取报表,还可以方便的导出为Excel、Pdf、Word,与.NET 完美集成,强烈推荐。

  • 未来改进

分布式:对一个大系统,分布式是必须的。即数据分别在不同节点采集、存储,但形式上依然是一个整体,可以统一查询和传输。

内存映像:目前的测点缓存模式存在可靠性不足的问题(如突然断电或系统崩溃造成的数据丢失),可依赖Sqlite和内存映像解决。

MQTT:物联网通行的MQTT协议可以解决不同系统之间的实时订阅传输问题。

查询扩展:原生支持按时间间隔分组取出数据等常用查询场景,可以有效提高查询性能。

安全控制:采用证书认证方式,加强权限管理,防止数据传输过程中被篡改。

数据归档流程:

开源纯C#工控网关+组态软件(七)数据采集与归档

五、   下面的计划

  • 网关层接口概述
  • 上下位机通讯原理
  • 如何实现一个设备驱动
  • 如何设计图元
  • 数据采集与归档
  • VS插件模块及原理
  • 归档模块及文件格式
  • 如何进行功能扩展
  • 组态变量表达式实现

github地址:https://github.com/GavinYellow/SharpSCADA。QQ群:102486275

开源纯C#工控网关+组态软件(七)数据采集与归档的更多相关文章

  1. 开源纯C#工控网关+组态软件

    一.   前言 在园子潜水也七八年了.说来惭愧,这么多年虽然一直自称.NET铁杆粉丝,然仅限于回几个不痛不痒的贴,既没有发布过代码,也没有写过文章. 看着.NET和C#在国外风生水起,国内却日趋没落, ...

  2. 开源纯C#工控网关+组态软件(二)工控网关的实现

    一.   工控网关是什么 网关是物联网和工控系统的核心组件.网关起的是承上启下的作用.上即上位机,电脑/触屏监控系统.MES这些:下即下位机,包括PLC.传感器.嵌入式芯片等. 不同厂家的下位机,往往 ...

  3. 开源纯C#工控网关+组态软件(八)表达式编译器

    一.   引子 监控画面的主要功能之一就是跟踪下位机变量变化,并将这些变化展现为动画.大部分时候,界面上一个图元组件的某个状态,与单一变量Tag绑定,比如电机的运行态,绑定一个MotorRunning ...

  4. 开源纯C#工控网关+组态软件(九)定制Visual Studio

    一.   引子 因为最近很忙(lan),很久没发博了.不少朋友对那个右键弹出菜单和连线的功能很感兴趣,因为VS本身是不包含这种功能的.   大家想这是什么鬼,怎么我的设计器没有,其实这是一个微软黑科技 ...

  5. 开源纯C#工控网关+组态软件(十)移植到.NET Core

    一.   引子 写这个开源系列已经十来篇了.自从十年前注册博客园以来,关注了张善友.老赵.xiaotie.深蓝色右手等一众大牛,也围观了逗比的吉日嘎啦.精密顽石等形形色色的园友.然而整整十年一篇文章都 ...

  6. 开源纯C#工控网关+组态软件(三)加入一个新驱动:西门子S7

    一.   引子 首先感谢博客园:第一篇文章.第一个开源项目,算是旗开得胜.可以看到,项目大部分流量来自于博客园,码农乐园,名不虚传^^. 园友给了我很多支持,并提出了很好的改进意见.现加入屏幕分辨率自 ...

  7. 开源纯C#工控网关+组态软件(四)上下位机通讯原理

    一.   网关的功能:承上启下 最近有点忙,更新慢了.感谢园友们给予的支持,现在github上已经有.目标是最好的开源组态,看来又近一步^^ 之前有提到网关是物联网的关键环节,它的作用就是承上启下. ...

  8. 开源纯C#工控网关+组态软件(六)图元组件

    一.   图元概述 图元是构成人机界面的基本单元.如一个个的电机.设备.数据显示.仪表盘,都是图元.构建人机界面的过程就是铺排.挪移.定位图元的过程. 图元设计是绘图和编码的结合.因为图元不仅有显示和 ...

  9. 开源纯C#工控网关+组态软件(五)从网关到人机界面

    一.   引子 之前都在讲网关,不少网友关注如何实现界面.想了解下位机变量变化,是怎样一步步触发人机界面动画的. 这个步步触发,实质上是变量组(Group)的批量数据变化(DataChange)事件, ...

随机推荐

  1. Hibernate之一对多(多对一)

    一.双向关联级联保存客户订单 1.搭建环境,项目结构如下 2.代码及配置如下(数据库里订单表不能用order,因为order是数据库关键字)(客户外键cid和订单表外键cid要在配置中写一致) pac ...

  2. MFC 最大化 的时候控件 按比例变大

    在dlg类头文件中声明CPoint Old; 在BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()声明一个映射:ON_WM_SIZE() 这样以后就可以在M_SIZE事件的时候 ...

  3. Twitter Storm中Bolt消息传递路径之源码解读

    本文初次发表于storm-cn的google groups中,现以blog的方式再次发表,表明本人徽沪一郎确实读过这些代码,:). Bolt作为task被executor执行,而executor是一个 ...

  4. [Javascript + lodash] sortBy and sortedIndex

    sortBy: var collection = ['John', 'Petteri', 'Antti', 'Joonas', 'Zhentian']; var sorted = _.sortBy(c ...

  5. java内存

    java内存分为四部分: 1).栈区(stacksegment),由编译器自动分配释放,存放函数的参数值和局部变量的值等,具体方法执行结束之后,系统自动释放JVM内存资源: 2).堆区(heapseg ...

  6. php 钩子函数原理 解析

    目前对钩子的理解:<转载:http://www.cnblogs.com/del/archive/2008/02/25/1080825.html> 譬如我们用鼠标在某个窗口上双击了一次, 或 ...

  7. 带你走进EJB--MDB

    在之前的文章中我们介绍了带你走进EJB--JMS 和 带你走进EJB--JMS编程模型 对JMS有了初步的了解, 作为EJB系列的文章我们会继续对EJB相关的内容做进一步深的学习和了解.而此次需要进行 ...

  8. &lpar;21&rpar;IO流之对象的序列化和反序列化流ObjectOutputStream和ObjectInputStream

    当创建对象时,程序运行时它就会存在,但是程序停止时,对象也就消失了.但是如果希望对象在程序不运行的情况下仍能存在并保存其信息,将会非常有用,对象将被重建并且拥有与程序上次运行时拥有的信息相同.可以使用 ...

  9. Python 字典和集合

    泛映射类型 collections.abc 模块中有 Mapping 和 MutableMapping 这两个抽象基类,它们的作用是为 dict 和其他类似的类型定义形式接口(在Python 2.6 ...

  10. 栈详解及java实现

    导读 栈和队列是有操作限制的线性表. 目录 1.栈的概念.特点.存储结构. 2.栈的java实现及运用. 概念 栈是一种只允许在一端进行插入或删除的线性表. 1.栈的操作端通常被称为栈顶,另一端被称为 ...