一种高效的传感器历史数据归档方法与流程

文档序号:18319671发布日期:2019-08-03 10:20阅读:394来源:国知局
一种高效的传感器历史数据归档方法与流程

本发明涉及信息处理技术领域,尤其涉及传感器产生的历史数据的归档所采取的技术方法,可以应用于物联网环境下历史数据管理系统研发的软件开发技术领域,属于信息技术领域。



背景技术:

物联网(Internet of things,IoT),也被称为传感网,是新一代信息技术的重要组成部分,也是信息化时代下各国研究者和产业界的研究热点。

市场研究公司ABIResearch最新发布的最新统计数据显示,目前物联网上的无线连网设备总数已经超过了100亿台,它预计这一数据将在2020年之前再增加两倍,达到300亿台。如此庞大数量的设备,其中由传感器产生的历史数据(以下简称传感器历史数据)也是海量的,而这些传感器历史数据具有数据量大、数据类型复杂、数据具有异构性、高度动态性、不完整性等特点,面对这样一种特性的历史数据,管理和利用好这些数据有着非常重要的意义。

因为传感器历史数据的数据量大且更新频繁,如果没有一种高效的数据归档方法,那么在软件研发过程中,数据归档模块将成为整个软件运行流程中的瓶颈所在。在一些对时间要求不太严格的环境下,一般的数据归档方法可以满足用户需求,但当面对那些对时间要求比较严格的环境中,一种高效的数据归档方法就显得十分必要了。

目前针对传感器历史数据归档的方法,大多数只提到了数据的压缩,尽管一个好的数据压缩算法固然很重要,但是数据归档并不仅仅是数据的压缩,还应当包括数据存入缓存及数据写入磁盘的过程。数据归档的完整的流程应该是先获取数据存入缓存,然后进行数据压缩,最后从缓存写入磁盘,这样才算完成了数据归档。

首先,数据在缓存中的存取效率是很高的,一般情况下,在缓存中的读写并不会在时间上存在瓶颈,但是,缓存在空间上一般都有限制,所以设计一种有效的方法来保证缓存不会被数据填满而导致数据覆盖是很有必要的。其次,对历史数据的压缩可以有效的减少数据的存储量,这样不仅可以减少数据的I/O操作,还能节省磁盘的存储空间,所以一种高效的历史数据压缩算法是数据归档过程中必不可少的一部分。最后,数据从缓存中写入到磁盘中,才算完成了归档的过程,这一过程往往会成为时间的瓶颈所在,因为磁盘的读写效率比缓存要低很多,这个写过程就受限于磁盘的写性能,采取固态硬盘固然是一种可行的方案,但是实际场景中基本都还在使用机械硬盘,所以要从实际情况出发提升效率的话,还是需要设计一种方法来尽量减少机械硬盘的寻道时间,从而使写性能尽可能的高。

综上所述,数据归档的效率受限于多个方面,为了使得传感器历史数据的归档效率尽可能的高,除了使用好的硬件,更为重要的是在软件方面提供一个高效的归档方法。



技术实现要素:

本发明针对传感器历史数据的数据量大、数据类型复杂、数据具有异构性、高度动态性、不完整性等特点,提出了一种能够对传感器历史数据归档的高效方法。

1.本发明要解决的技术问题

首先,在某一时刻可能有百万的传感器同时产生历史数据,针对一次性到来的如此庞大的数据,我们不仅要保证数据的快速归档,还要保证数据不被覆盖,这是传感器历史数据归档中最显而易见也是最需要解决的问题。其次,因为传感器历史数据是随着时间的推移而变得越来越多的,是海量的数据,在这个过程中,如何有效的选择具有代表性的数据进行归档也是一个需要解决的问题。最后,在缓存中数据的传递效率是非常高的,但是从缓存到磁盘文件,数据传递效率是很低的,这是由磁盘本身的性质决定的,如何设计文件结构尽可能的让数据从缓存快速的存入文件也是需要解决的一个大问题。

为了解决上述问题,本发明提出了一种能够对传感器历史数据进行归档的方法,该方法采用多级缓存策略,并设计了一种链式文件存储结构,能够有效的解决上述问题。

2.本发明方法的步骤

一种高效的传感器历史数据归档方法,包括以下步骤:

1)获取传感器数据存入缓存中,确保在这个过程中不会出现未归档数据被覆盖冲刷掉,存储这部分原始数据的缓存,我们称之为一级缓存,一级缓存能够保证历史数据尽可能快的存入,并且防止数据覆盖,只要一级缓存未满,数据就可以存入,在数据存入的同时,会有另外一个线程不断地从一级缓存中取走数据,以保证一级缓存一直保持在一个可存入数据的状态,它的具体设计方式将在下文的具体实施方式中说明。

2)对一级缓存中的历史数据进行压缩,具体的压缩策略可根据实际情况选择,但是压缩过程必不可少。压缩过程抛弃了不具有代表性的数据,只留下了具有代表性的数据待归档,具有代表性的数据是指在今后进行数据还原时,这部分数据可以在一定误差允许范围内还原出被抛弃的数据。然后这部分具有代表性的数据被放入一个新的缓存中,我们称之为二级缓存,二级缓存不断地接收数据的同时,也会有一个线程同时进行判断并从二级缓存中取走数据,它的具体设计方式将在下文的具体实施方式中说明。

3)从二级缓存中取出数据后,这些数据待写入文件中。倘若以传统的方式,同一个传感器的历史数据都存放在某一个文件中,这样n个传感器就至少会产生n个数据文件,n的值往往很大,就会产生大量的历史数据文件,影响数据从缓存写入文件的效率。为了减少历史数据文件的数量,同时保证数据尽可能快速的写入文件,每个历史文件的文件存储结构被设计为一种链式文件存储结构,对于新来的数据,都是添加在当前未满历史文件末尾,只有在当前历史文件大小超过限制(用户设定)时,才会创建新的历史数据文件继续存储后续数据。这样的链式文件结构设计有效的减少了历史文件的数量,同时提升了数据写入文件的效率。在具体实现时,我们会为每一个历史数据缓存块申请一个对应的历史数据索引块,索引块之间利用指针信息串联成一个链,每个索引块内还有一些关于对应缓存块的信息,例如对应缓存块的块头在文件中的位置、对应缓存块的块大小等等,用于找到历史数据缓存块。假如某个传感器在文件中有历史数据缓存块d1,d2,…,dn-1,dn这n个缓存块,对应的历史数据索引块为p1,p2,…,pn-1,pn,则p1,p2,…,pn-1,pn之间会利用一个数据元素pNext指针串联起来,每个pi内会有一个数据元素pData指向di的起始位置,还有一个数据元素iDataOff指示di的大小(1<=i<=n)。除了链式文件结构的设计之外,在缓存数据写入文件之前,会对待写入文件的缓存数据以及待修改的文件数据先进行一些预处理操作,预处理操作用来保证数据在写入文件过程中,尽可能的按照数据在文件中待写入或待修改的位置的先后顺序写入。例如,缓存块和索引块的待写入位置的先后顺序,是依照待写入缓存块的被存满的时间先后顺序来确定的,此外,每次写入了新的缓存块之后,还要修改其上级索引块的指针内容,在修改的时候,是依照上级索引块在文件中位置的先后顺序来进行修改的。在不考虑操作系统本身对写文件的优化情况下,按照数据在文件中待写入位置的先后顺序写入文件可以避免写文件时位置的来回跳转,例如:

在文件中p1和p2两个位置需要写入数据,p1在p2之前,p1和p2之间的距离为d1,p2到文件末尾的距离为d2。

假如先写p2再写p1,然后移动到文件末尾,则寻址距离为:d1+d1+d2。

假如先写p1再写p2,然后移动到文件末尾,则寻址距离为:d1+d2。

从上面可以看出,按序写入减少了寻址的距离,大大地节省了历史数据从缓存写入文件的时间。

与现有技术相比,本发明方法具有的有益技术效果:

1)本发明提出了一种从数据进入缓存到写入磁盘的完善的归档方法,该方法针对传感器历史数据的归档具有高效率的表现。

2)本发明针对传感器历史数据的特点,采用了一种多级缓存的策略,可以有效的避免读数据和写数据之间的互斥而产生的时间延迟问题。

3)本发明采用了以缓存块为基本单元的方式,只有当缓存块满了之后才取出并进行下一步操作,这样有效的减少了磁盘I/O的次数,提升了归档的效率。

4)本发明针对传感器历史数据产生的时间特性,设计了一种链式文件存储结构,使得数据能够快速的从缓存归档到文件中。

附图说明

图1为传感器产生历史数据记录的示意图;

图2为本发明方法的缓存块示意图;

图3为本发明方法的一级缓存的缓存块与二级缓存的缓存块区别示意图;

图4为本发明方法的整体流程示意图;

图5为本发明方法的一级缓存工作方式示意图;

图6为本发明方法的二级缓存工作方式示意图;

图7为本发明方法的文件存储结构示意图;

图8为某个传感器的历史数据在文件中的存储结构示意图;

图9为本发明方法中数据从二级缓存到写入文件的处理策略示意图;

图10为本发明方法的数据在文件中写入或修改的顺序示意图。

具体实施方式

以下结合附图详细描述本发明所提供的高效的传感器历史数据归档方法:

1.传感器生成历史数据的过程

如图1所示,每个传感器都相当于一个数据源,在任意时刻ti均有可能产生一条历史数据记录,一条历史数据记录在经过数据采集接口的预处理之后,仅保留部分必要的数据元素,本方法中,保留的必要数据元素有5个:数据类型、数据值、数据状态、产生数据的传感器编号、产生数据的时间。在程序中,每个缓存块中可以存放多条历史数据,每条历史数据记录对象所包含的数据内容如下:

2.数据缓存块结构说明

如图2所示,缓存块是指可以存放多条历史数据记录的一块内存区域,历史数据记录在缓存块中是紧挨着依次存放的。正常情况下,一级缓存以及二级缓存中的缓存块的大小是由用户配置的,并且设置为一条历史数据记录大小的整数倍。缓存块满指的是缓存块无法再放入一条历史数据记录。

3.一级缓存与二级缓存的区别

如图3所示,虽然一级缓存和二级缓存都是包含多个缓存块的缓存区域,但是二者还是有区别的。一级缓存中,只要当前使用的缓存块没满,任何一个传感器产生的历史数据记录都可以放入当前使用的缓存块的;而在二级缓存中,缓存块i中只存放传感器i产生的历史数据记录,是一个一一对应关系。

4.本发明方法的整体工作流程

如图4所示,本发明方法主要涉及三个部分:1、获取数据存入到一级缓存中;2、从一级缓存取出数据后进行压缩,然后存入二级缓存;3、从二级缓存中取出多个被存满的缓存块,经过预先分配位置及排序等预处理操作之后写入历史文件中,待写入位置根据被存满的时间先后顺序确定。

5.一级缓存工作方式

如图5所示,一级缓存中以缓存块为基本单元,是一个首尾相连的环形缓存块队列。环形缓存队列又可划分为两部分,分别为满缓存块队列和空闲缓存块队列两部分。每次从空闲缓存块队列中取出第一个作为当前缓存块使用,如果当前缓存块满,则将其标记为满缓存块,成为满缓存块队列的一部分,然后接着从空闲缓存块队列取出第一个缓存块作为当前缓存块。在存储数据的同时,还有一个并行的取数据线程不断地从满缓存块队列中取走数据,每次都取走最早的那个满缓存块(最早的满缓存块是指在时间上最先被写满的缓存块)的数据,当这个满缓存块数据被取走,该缓存块成为空闲缓存块队列的一部分。

6.二级缓存工作方式

如图6所示,二级缓存中依旧是以缓存块为基本单位,从一级缓存中取出的数据经过压缩处理之后,具有代表性的数据会被存入二级缓存块中,压缩过程必不可少,但是采用的压缩策略可根据实际情况来定,可以是有损压缩或者无损压缩。不同于一级缓存中的块,二级缓存中编号为i的缓存块只会存放传感器i所产生的历史数据记录。编号为i的缓存块有两个,为了便于说明,我们分别称它们为常用缓存块和临时缓存块。常用缓存块common_i是用来和取数据线程进行交互的,当常用缓存块common_i满时,此时常用缓存块common_i会被放入待写入文件的缓存块队列中,常用缓存块common_i暂时不可用。临时缓存块tmp_i的作用,就是在常用缓存块common_i等待数据被取走的过程中,临时存储传感器i新产生的历史数据。

在常用缓存块common_i等待数据被取走的过程中,若直到常用缓存块common_i的数据完全被取走,临时缓存块tmp_i也没有被数据填满,那么常用缓存块common_i的数据被取走之后,临时缓存块tmp_i中的数据会被拷贝到常用缓存块common_i中去,同时临时缓存块tmp_i清空,新来的历史数据记录存到常用缓存块common_i中。但是,如果在任意常用缓存块common_i的数据完全被取走之前,对应临时缓存块tmp_i已被数据填满,则此时取数据操作会被暂时阻塞,直到常用缓存块common_i的数据被取走,临时缓存块tmp_i中的数据拷贝到常用缓存块common_i中,临时缓存块tmp_i清空,阻塞才会取消。此时,因为临时缓存块tmp_i是满缓存块拷贝到常用缓存块common_i中,常用缓存块common_i依旧是满的,所以取数据操作将新来的历史数据记录继续放到临时缓存块tmp_i中,而常用缓存块common_i依旧在待写入文件的缓存块队列中。

与写文件线程交互的始终是常用缓存块common_i,无论何时,写文件线程都是从常用缓存块common_i取出待写入文件的数据,临时缓存块tmp_i只是起到一个缓冲作用,尽可能保证不出现数据阻塞,常用缓存块common_i的数据一旦被取走,临时缓存块tmp_i中的数据会放到常用缓存块common_i中。从前文描述可知,当常用缓存块common_i满且数据未被取走,同时临时缓存块tmp_i也写满时,再到来传感器i的数据就会出现阻塞,但是,这个阻塞过程是不常见同时非常短暂的。首先,因为一级缓存中的缓存块数据是经过压缩算法之后再输入到二级缓存中的,所以一级缓存的一个缓存块中需要存储的数据可能只占很小一部分,其次,内存中的数据读写操作速度很快,因此,只要缓存块大小设置得当,在常用缓存块common_i等待的过程中出现临时缓存块tmp_i被填满而导致数据阻塞的现象并不会频繁出现,即使出现了,这个阻塞时间也是非常短的。

7.文件存储结构

如图7所示,为了尽可能快的将传感器历史数据从缓存中写入磁盘中,本方法中的文件存储结构被设计成单链表结构,存储方式为按二级缓存块被写满的时间先后顺序在文件末尾依次存储。为了便于理解,在下文中,缓存块在写入文件后我们依旧称其为缓存块。每个待写入文件的缓存块对应一个索引块,每个索引块对象包含的数据内容如下:

为了更加清楚的说明文件中数据的结构,以传感器i产生的历史数据为例,如图8所示,索引块之间用pNext链接构建成了一个单链表结构,它指向下一级索引块块头在文件中的偏移位置,每个索引块有一个指针pData,它指向对应的缓存块块头在文件中的偏移位置,为了便于描述,在下文中,我们称一个单链表中相邻的两个索引块中的前一个索引块为上级索引块。如图7、图8所示,在链式文件结构中,除了起始的索引块与对应缓存块写入文件后的位置是分开的,其余的索引块与对应缓存块在写入文件后都是相邻的,索引块在前,对应缓存块在后。起始索引块会与对应缓存块在写入后在位置上不相邻,是因为所有传感器的历史数据的起始索引块是预先连续分配在文件开始部分的,该设计的目的是便于以后查找某个传感器的历史数据记录时,可以快速定位到起始索引块。

8.从二级缓存到磁盘文件

如图9所示,因为数据从内存写入文件速度是比较慢的,所以在进行下一次数据写入文件之前,可能二级缓存中已经有n(n>1)个缓存块满了等待写入文件,那么就会出现一次性到来n(n>1)个满缓存块需要写入文件的情况,如果是按照一个缓存块接着一个缓存块去存储,即针对一个缓存块,先在文件末尾写入它的索引块及缓存块本身,再修改上级索引块的pNext,使得上级索引块的pNext指向当前索引块,在这些操作完成之后,再进行下一个缓存块的操作,那么,就会造成写文件时位置的来回跳转,在不考虑操作系统本身对写文件的一些优化操作的情况下,就会造成在寻址上过多的时间消耗。

因此,在本方法中,采取预先计算好n个缓存块及n个索引块在文件中需要写入的位置,然后根据待写入位置分别对索引块和缓存块进行排序,在此之后,本方法中,是先一次性按序写入n个缓存块,再一次性按序写入n个索引块,最后,再根据上级索引块在文件中的先后顺序(排序)依次修改上级索引块的pNext(没有下一级时,默认是未指向任何地方,例如设置为空),使其指向本级索引块。上述三步操作可调整顺序,但是建议先写入缓存块,这样保证在程序异常退出时数据得到最大程度的保留。通过预先分配好写入地址,排序后按序写入数据,有效避免了写文件过程中位置的频繁跳转,减少了寻址时间。

如图10所示,在经过预先处理之后,先一次性按序写入缓存块,再一次性写入索引块,最后一次性修改上级索引块的链接,这个过程最多只有3次的文件位置的来回跳转,当有多个满缓存块需要同时写入文件时,本方法的高效率将得以体现。另外,将历史数据缓存块最先写入文件,再进行索引块的链接及写入操作,这样的顺序安排,可以保证异常时,历史数据尽可能多的保存到文件中。

以下将具体说明本方法的实施方式:

1.一级缓存的分配及初始化

在内存中开辟n+1个相同大小的块作为一级缓存,n根据实际情况作相应调整,每个缓存块的大小根据实际情况进行相应调整,其中第n+1个缓存块是一个优化操作,用来专门用来将数据放入二级缓存的,称为专用缓存块,假如有缓存块i写满了,先将缓存块n+1置为空,再将指向缓存块i的指针与指向缓存块n+1的指针进行指针交换,这样,满缓存块就放入了第n+1个缓存块中,缓存块i又可以继续使用了,指针交换的效率非常高,可以让每个满缓存块i都能尽快的被再次使用。再设置两个指针,m_lFirstIndex指向最早的那个满数据的缓存块(满数据不一定是块完全满,而是无法再写入一条历史数据),m_lCurrentIndex指向当前使用的数据缓存块,除此之外,还需要一些信号量及互斥量保证写数据线程和取数据线程之间的同步,写数据线程类似于生产者,取数据线程类似于消费者,生产者与消费者的同步问题不再赘述,有很多方法。代码及说明如下:

1)m_lDataBufferCount=lDataBufferCount;

2)m_pBuffer=malloc(sizeof(DataBuffer)*(m_lDataBufferCount+1));

//采用指向指针的指针进行操作,是因为在之后读写的时候会修改指针指向

3)m_ppDataBuffers=(DataBuffer**)malloc(sizeof(DataBuffer*)*(m_lDataBufferCount+1));

4)m_ppDataBuffers[0]=(DataBuffer*)m_pBuffer;

5)for(int i=1;i<=m_lDataBufferCount;i++)

6){

7)m_ppDataBuffers[i]=m_ppDataBuffers[i-1]+1;

8)m_ppDataBuffers[i]->wDataLen=0;

9)}

//有数据的第一个缓存块下标

10)m_lFirstIndex=0;

//当前正在操作的缓存块下标

11)m_lCurrentIndex=0;

12)m_ppDataBuffers[m_lCurrentIndex]->wDataLen=0;

第1)、2)两行是分配一级缓存的相关操作,其中lDataBufferCount是一级缓存中除置换块以外的缓存块个数,DataBuffer是一块缓存块的数据结构。第3)行是将分配的一级缓存指针赋值给一个指针,是因为后续操作存在一级缓存指针的修改操作,因此在这里要使用指针的指针。第4)到8)行是对一级缓存进行初始化操作。第10)、11)两行是初始化m_lFirstIndex和m_lCurrentIndex操作,最开始时它们都是指向第一个缓存块。

2.传感器历史数据存入一级缓存

一级缓存写数据线程中,会将新来的历史数据不断地放入m_lCurrentIndex指向的缓存块。如果m_lCurrentIndex指向的缓存块未满,则直接将数据放入;如果已满,则判断下一个缓存块是否是空闲缓存块,如果是,那么m_lCurrentIndex指向下一个缓存块,否则,等待,直到下一个缓存块为空闲缓存块:

//如果当前缓存块无法放下本条记录

1)if(m_ppDataBuffers[m_lCurrentIndex]->wDataLen+wDataLen>=HIS_SYS_VALUE_BUF FER_SIZE)

2){

//如果已经没有新的空闲缓存块可以使用

3)if((m_lCurrentIndex+1)%m_lDataBufferCount==m_lFirstIndex)

4){

5)Waiting();

6)}

//如果有新的空闲缓存块可以使用

7)else

8){

//使用下一个缓存块作为当前缓存块

9)m_lCurrentIndex=(m_lCurrentIndex+1)%m_lDataBufferCount;

10)}

11)}

//在当前缓存块的末尾写入历史数据记录

12)memcpy(m_ppDataBuffers[m_lCurrentIndex]->lpData+m_ppDataBuffers[m_lCurrentIndex]->wDataLen,pData,wDataLen);

//修改当前缓存块数据长度信息

13)m_ppDataBuffers[m_lCurrentIndex]->wDataLen+=wDataLen;

第1)行是指当前缓存块已经无法再放入新到来的一条历史数据记录了,在这种情况下,如果再无缓存块可用,那么写数据操作将阻塞,如代码3)到6)行;但是如果是有空闲缓存块可用的,那么将当前缓存块替换为第一个空闲缓存块,如代码7)到10)行。第12)、13)行是将历史数据记录放到缓存块末尾,并修改缓存块当前使用的大小。

3.取数据线程从一级缓存中取出数据

在一级缓存写数据的同时,取数据线程会并行操作,取数据线程的工作是将m_lFirstIndex指向的块与第n+1个块的进行指针交换,使得第n+1个块指向满缓存块,然后取数据线程直接从第n+1个缓存块取数据,具体实现如下:

//如果第一个m_lFirstIndex指向的块为空,说明现在不存在有数据的缓存块

1)if(m_ppDataBuffers[m_lFirstIndex]->wDataLen==0)

2){

3)return NULL;

4)}

//否则,将m_lFirstIndex指向的缓存块与m_lDataBufferCount指向的缓存块进行指针交换

5)DataBuffer*p;

6)m_ppDataBuffers[m_lDataBufferCount]->wDataLen=0;

7)p=m_ppDataBuffers[m_lFirstIndex];

8)m_ppDataBuffers[m_lFirstIndex]=m_ppDataBuffers[m_lDataBufferCount];

9)m_ppDataBuffers[m_lDataBufferCount]=p;

//m_lFirstIndex指向下一个缓存块

10)m_lFirstIndex=(m_lFirstIndex+1)%m_lDataBufferCount;

//返回第n+1个缓存块

11)return m_ppDataBuffers[m_lDataBufferCount];

第1)到4)行是一种特殊情况,是当取数据线程的取数据速度远快于写数据线程的写数据速度的时候,没有缓存块有数据。第5)到11)行描述的是,除特殊情况外,将m_lFirstIndex指向的缓存块与第m_lDataBufferCount个缓存块(也就是前文所述的第n+1个缓存块)进行交换,然后m_lFirstIndex指向下一个缓存块,第m_lDataBufferCount个缓存块返回给取数据线程。

4.二级缓存的分配及初始化

在二级缓存中,传感器i所产生的历史数据记录放入对应的常用缓存块common_i中,而每个常用缓存块又对应一个索引块,因此,首先,根据传感器的个数m在内存中开辟m个常用缓存块以及m个索引块。其次,因为在常用缓存块common_i不可用时需要临时缓存块tmp_i进行中转,所以还需要开辟m个对应的临时缓存块,但是这m个临时缓存块并没有分配内存,而是采用懒加载的方式,在具体需要使用时再从内存分配,这样可以节省内存的使用量。最后,还需要一个结构标记常用缓存块是否可用:

1)ppActDatas=(structActData*)malloc(SysCfg.iPointNum*SysCfg.iDataBlockSize*sizeof(structActData));

//申请SysCfg.iPointNum个索引块

2)pActIdxs=(structIdx*)malloc(SysCfg.iPointNum*sizeof(structIdx));

...

3)m_ppDataCache=(void**)calloc(SysCfg.iPointNum,sizeof(void*));

4)m_plDataCacheCount=(long*)calloc(SysCfg.iPointNum,sizeof(long));

5)m_pbIsArchivingPn=(BOOL*)calloc(POINTNUM,sizeof(BOOL));

第1)行是分配与传感器个数相同的常用缓存块,其中SysCfg.iPointNum是传感器数,SysCfg.iDataBlockSize*sizeof(structActData)是缓存块大小。第2)行分配与常用缓存块对应的索引块。第3)行分配临时缓存块结构,但是并未分配实际的内存。第4)行分配的是一个用来记录临时缓存块中数据大小的动态数组。第5)行分配的是一个bool类型的动态数组,用来标记对应的常用缓存块是否可用。

5.一级缓存中获取的数据存入二级缓存中

从一级缓存取来的数据,首先要经过压缩操作,筛选出具有代表性的历史数据数据,具体压缩算法不再赘述,可根据实际需求进行选择,本方法中采用的是一种高压缩率的有损压缩算法。在压缩操作过后,具有代表性的历史数据记录将被写入对应的二级缓存中:

void PutIntoBlock(structInputData*pData)

{

//从一级缓存取数据,根据数据记录的iPn(即传感器编号)存入对应的常用缓存块中

1)(ppActDatas+pData->iPn*SysCfg.iDataBlockSize+pActIdxs[pData->iPn].iDataOff)->bState=pData->bState;

2)(ppActDatas+pData->iPn*SysCfg.iDataBlockSize+pActIdxs[pData->iPn].iDataOff)->dValue=pData->dValue;

3)(ppActDatas+pData->iPn*SysCfg.iDataBlockSize+pActIdxs[pData->iPn].iDataOff)->iTime=pData->iTime;

//新放入一条数据,因此该缓存块的偏移增加

4)pActIdxs[pData->iPn].iDataOff++;

//修改索引块中的时间信息

5)if((pActIdxs[pData->iPn].iStime==0)||(pActIdxs[pData->iPn].iStime>pData->iTime))

6){

7)pActIdxs[pData->iPn].iStime=pData->iTime;

8)}

9)pActIdxs[pData->iPn].iEtime=__max(pActIdxs[pData->iPn].iEtime,pData->iTime);

//如果缓存块已满,则该缓存块要放入待写入文件队列

10)if(pActIdxs[pData->iPn].iDataOff==SysCfg.iDataBlockSize)

11){

//缓存块已满,该缓存块放入待写文件队列,缓存块不可用

12)PutArchivePn(pData->iPn,FALSE);

}

}

第1)至3)行是将数据记录放到iPn(即传感器编号)对应的常用缓存块iPn中,第4)行是修改索引块iPn中的偏移信息,它指示的是对应常用缓存块iPn中历史数据记录的条数,目前新放入了一条历史数据记录,因此要加1。因为索引块iPn中记录着对应常用缓存块的历史数据记录的起止时间,新放入一条历史数据记录之后,需要修改起止时间,第5)至9)行就是对起止时间修改的操作。常用缓存块iPn新放入一条历史数据记录之后,还要判断该常用缓存块是否已满,如果满了,则要将该常用缓存块放到待写文件缓存块队列中,第10)至12)行就是完成此功能。

6.取数据线程从二级缓存中取数据

当二级缓存的常用缓存块满时,它暂时无法使用,此时数据将被存入对应的临时缓存块中,等到常用缓存块中数据被取走,此时再将临时缓存块的数据放入常用缓存块:

//若常用缓存块正在待写入文件队列中,即常用缓存块不可用

while(m_pbIsArchivingPn[pData->iPn])

{

//将数据放入对应的临时缓存块

//临时缓存块未使用,则初始化

1)if(m_ppDataCache[pData->iPn]==NULL)

2){

3)m_ppDataCache[pData->iPn]=malloc(SysCfg.iDataBlockSize*sizeof(structInputData);

4)}

//临时缓存块未满,则数据放入临时缓存块

5)if(m_plDataCacheCount[pData->iPn]<SysCfg.iDataBlockSize)

6){

7)memcpy(((structInputData*)m_ppDataCache[pData->iPn])+m_plDataCacheCount[pData->iPn],pData,sizeof(structInputData));

8)m_plDataCacheCount[pData->iPn]++;

9)Unlock();

10)return;

11)}

//临时缓存块满了,则阻塞,等待常用缓存块数据被写入文件

12)else

13){

14)Waiting();

15)}

}

//常用缓存块可用了

//若临时缓存块不为空,则将临时缓存块中的数据拷贝到常用缓存块

if(m_plDataCacheCount[pData->iPn]>0)

{

//依次将临时缓存块中的数据拷贝到常用缓存块

16)for(int i=0;i<m_plDataCacheCount[pData->iPn];i++)

17){

18)PutIntoBlock(((structInputData*)m_ppDataCache[pData->iPn])+i);

19)}

20)m_plDataCacheCount[pData->iPn]=0;

21)free(m_ppDataCache[pData->iPn]);

22)m_ppDataCache[pData->iPn]=NULL;

}

//常用缓存块不满,新数据放到常用缓存块

if(pActIdxs[pData->iPn].iDataOff!=SysCfg.iDataBlockSize)

23)PutIntoBlock(pData);

else

{

24)m_ppDataCache[pData->iPn]=malloc(SysCfg.iDataBlockSize*sizeof(structInputData);

25)memcpy(((structInputData*)m_ppDataCache[pData->iPn])+m_plDataCacheCount[pData->iPn],pData,sizeof(structInputData));

26)m_plDataCacheCount[pData->iPn]++;

}

第1)至15)行是在常用缓存块iPn不可用的情况下所进行的操作,其中第1)至4)行是用来从内存中分配实际的空间给临时缓存块iPn,第5)至11)行是若指临时缓存块iPn未满,则将数据放到临时缓存块中,而第12)至15)行是指若临时缓存块iPn也满,此时数据阻塞,需要等待常用缓存块iPn中数据被写入文件,常用缓存块iPn可用之后才不再阻塞。

第16)至22)行就是在常用缓存块iPn可用之后,若临时缓存块中iPn也有数据,就将临时缓存块iPn中的数据拷贝到常用缓存块iPn中去,同时清空临时缓存块。临时缓存块iPn中数据拷贝到常用缓存块iPn中也是分两种情况的,一种是拷贝后的常用缓存块iPn未满,另一种就是拷贝后的常用缓存块iPn满,若是前者,就将新来的数据记录放到常用缓存块iPn中,如23)行所示;若是后者,则新来的数据继续放到临时缓存块iPn中,如24)至26)行所示。

7.写文件时所需数据结构的分配及初始化

写文件线程在写文件过程中涉及修改索引块的pNext信息,因此需要一个保存上级索引块的地址的数据结构和一个保存当前索引块地址的数据结构:

//当前写入文件的索引块在文件中的地址

1)pActIdxsAdr=(long long*)calloc(SysCfg.iPointNum,sizeof(long long));

//上一级索引块在文件中的地址

2)pActIdxsPreAddr=(long long*)calloc(SysCfg.iPointNum,sizeof(long long));

第1)行分配的pActIdxsAdr用来存储每个传感器当前写入文件的索引块在文件中的地址,第2)行分配的pActIdxsPreAddr是用来存储每个传感器的上一级索引块在文件中的地址。

8.预先给缓存块分配待写入位置并排序

在写文件线程中,从二级缓存中取出待写入文件队列中的缓存块,对这些缓存块及其索引块预先分配要写入的位置。在这里有个特殊情况,因为文件头部连续预留了m个传感器的第一个索引块,因此如果是传感器第一个待写入文件的缓存块,那么它的索引块就在文件头部,并且它是不存在上级索引块的,不需要进行上级索引块的pNext修改:

//二级缓存中待写入文件的缓存块的数量

1)lPointCount=gpHisValueManageThread->GetArchivePn(ArchivePointInfos,MAX_ARCHIVING_POINT_COUNT);

for(index=0;index<lPointCount;index++)

{

//如果是传感器iPn的第一个缓存块,那么它是没有上一级索引块的

2)if(pActIdxsPreAddr[ArchivePointInfos[index].iPn]==0)

3){

//本级索引块的地址,在文件头部

4)pActIdxsAdr[ArchivePointInfos[index].iPn]=_BaseIdxAdr(SysCfg.iPointNum)+ArchivePointInfos[index].iPn*sizeof(structIdx);

//缓存块待写入的地址

5)pActIdxs[ArchivePointInfos[index].iPn].pData=iDfOffset;

//更新文件长度

6)iDfOffset+=sizeof(structData)*SysCfg.iDataBlockSize;

7)}

//如果不是传感器iPn的第一个缓存块

8)else

{

//将传感器编号及改传感器的上一级索引块地址保存,用于排序后修改pNext

9)pSortElementPreIdxArray[lPreIdxCount].iPn=ArchivePointInfos[index].iP n;

10)pSortElementPreIdxArray[lPreIdxCount].iKeyValue=pActIdxsPreAddr[Archi vePointInfos[index].iPn];

//需要修改pNext的上级索引块个数

11)lPreIdxCount++;

//本级索引块待写入的地址,在文件尾部

12)pActIdxsAdr[ArchivePointInfos[index].iPn]=iDfOffset;

//更新文件长度

13)iDfOffset+=sizeof(structIdx);

//缓存块待写入的地址,在文件尾部

14)pActIdxs[ArchivePointInfos[index].iPn].pData=iDfOffset;

//更新文件长度

15)iDfOffset+=sizeof(structData)*SysCfg.iDataBlockSize;

16)}

//存储缓存块待写入文件的地址及对应传感器编号

17)pSortElementDataArray[index].iPn=ArchivePointInfos[index].iPn;

18)pSortElementDataArray[index].iKeyValue=pActIdxs[ArchivePointInfos[index].iPn].pData;

//存储索引块待写入文件的地址及对应传感器编号

19)pSortElementPnArray[index].iPn=ArchivePointInfos[index].iPn;

20)pSortElementPnArray[index].iKeyValue=ArchivePointInfos[index].iPn;

}

//按照缓存块待写入的地址进行排序

21)SortArchivePoint(pSortElementDataArray,0,lPointCount-1);

//按照索引块待写入的地址进行排序

22)SortArchivePoint(pSortElementIdxArray,0,lPointCount-1);

//按照上级索引块的地址进行排序

23)SortArchivePoint(pSortElementPreIdxArray,0,lPreIdxCount-1);

第1)行是从二级缓存中获取待写入文件的缓存块的个数。第2)至7)行,假如当前待写入文件的缓存块是传感器iPn的第一个写入文件的缓存块,那么它的索引块地址在文件头部,缓存块待写入在文件尾。第8)至16)行,假如当前待写入缓存块不是传感器iPn的第一个写入文件的缓存块,首先保存上级缓存块的地址用于排序,然后索引块和缓存块依次在文件尾分配待写入位置。第17)至18)行,存储缓存块待写入文件的地址及对应传感器编号。第19)至20)行,存储索引块待写入文件的地址及对应传感器编号。第21)至23)行,对缓存块依据待写入的位置进行排序,对索引块依据待写入的位置进行排序,对上级索引块在文件中的位置进行排序。

9.写入文件的操作

在进行了预先分配地址和排序操作之后,就要按照排序顺序写入缓存块、修改上级块的pNext和写入当前索引块的操作了:

//依照缓存块在文件中预先分配的位置的先后顺序将缓存块写入文件

1)for(index=0;index<lPointCount;index++)

{

//k是排序后的待写入缓存块在二级缓存中的起始地址

2)k=pSortElementDataArray[index].iPn*SysCfg.iDataBlockSize;

//把数据从二级常用缓存块中拷贝出来

3)for(i=0;i<pActIdxs[pSortElementDataArray[index].iPn].iDataOff;i++){

4)(pOneBlockDatas+i)->bState=(ppActDatas+k+i)->bState;

5)(pOneBlockDatas+i)->dValue=(ppActDatas+k+i)->dValue;

6)(pOneBlockDatas+i)->iTime=(ppActDatas+k+i)->iTime;

7)}

//将数据写入到预先分配的位置

8)WriteData_ppActDatas(pOneBlockDatas,pActIdxs[pSortElementDataArray[index].iPn].pData,pActIdxs[pSortElementDataArray[index].iPn].iDataOff,dataf);

9)}

//依照上级索引块在文件中的先后顺序,修改上级索引块的next指针

10)for(index=0;index<lPreIdxCount;index++)

11){

//找到上级索引块的地址

12)fseek(dataf,pActIdxsPreAddr[pSortElementPreIdxArray[index].iPn]+2*sizeof(DWORD),SEEK_SET);

//在pNext位置写入当前索引块地址

13)fwrite(&(pActIdxsAdr[pSortElementPreIdxArray[index].iPn]),sizeof(lon g long),1,dataf);

14)}

//依照当前索引块在文件中待写入的位置顺序,将当前索引块写入文件

15)for(index=0;index<lPointCount;index++)

16){

17)WriteData_pPnActIdxs(pActIdxs+pSortElementIdxArray[index].iPn,pActIdx sAdr[pSortElementIdxArray[index].iPn],dataf);

18)}

第1)至9)行,根据缓存块在文件中待写入的位置的先后顺序,依次将缓存块写入文件。第10)至14)行,依照上级索引块在文件中的先后顺序,依次修改上级索引块的pNext指针。第15)至18)行,根据索引块在文件中待写入位置的先后顺序,依次将索引块写入文件。

10.写完文件之后对一些重要数据信息的更新

在一次写入操作完成之后,需要更新一些信息,最重要的就是更新上级索引块地址的值。因为每次写入操作完成之后,那些新写入了数据的传感器设备,都要更新上级索引块地址为本次写入的索引块的地址,用来下一次写入操作使用:

1)for(index=0;index<lPointCount;index++)

2){

3)//更新上级索引块地址为本次写入的索引块的地址

4)pActIdxsPreAddr[ArchivePointInfos[index].iPn]=pActIdxsAdr[ArchivePoint Infos[index].iPn];

5)}

如第1)至5)行所示,对于本次写入了缓存块的传感器iPn,我们需要修改它的上级索引块的地址为本次写入的的索引块地址,以便下一次写入数据再次使用。

当前第1页1 2 3 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1