我们怎样把 E 电力企业风机监控报表开发周期缩短 16 倍

问题背景

E 电力企业在风力发电系统中需要查询风力发电机运行状况(风机停机时间、停机时长、是否超过阈值等信息),在系统中通过报表(图表)输入时间和风机参数进行查询。

设备运行状态由工业设备和实时数据库(麦杰)采集记录,由于其数据过于原始,且格式无法直接用于报表查询,需要进行二次加工(标准化)存储到关系数据库再使用。

由于实时库的特殊性以及标准化算法异常复杂,以前只能用 JAVA\C# 等高级语言实现,不仅设计周期长,且难以实现难以维护。

问题分析

风机原始实时数据存储在麦杰数据库中,麦杰库没有 JDBC 接口,但有符合工业标准的 OPC 接口(OLE for Process Control)。通过该接口,JAVA 程序可获取某时间段内某风力发电机每秒(或每毫秒)的起停状态。数据格式如下:

DynamicData:   time:2017-07-05 15:06:00.000,AV:1.0,AS:0

DynamicData:   time:2017-07-05 15:35:00.000,AV:1.0,AS:0

DynamicData:   time:2017-07-05 15:38:00.000,AV:4.0,AS:0

DynamicData:   time:2017-07-05 15:44:00.000,AV:2.0,AS:0

DynamicData:   time:2017-07-05 15:44:01.000,AV:2.0,AS:-32768

DynamicData:   time:2017-07-05 15:53:00.000,AV:1.0,AS:0

DynamicData:   time:2017-07-05 15:54:00.000,AV:1.0,AS:0

DynamicData:   time:2017-07-05 15:59:00.000,AV:5.0,AS:0

DynamicData:   time:2017-07-05 15:59:01.000,AV:5.0,AS:-32768

DynamicData:   time:2017-07-05 15:59:02.000,AV:1.0,AS:0

 

上表中,有意义的字段是 time 和 AV,分别代表时间点和该时间点的风机状态,其中 AV 等于 1.0 表示开机,等于其他值表示各种原因导致的停机。

 

原始数据无法直接用于报表查询,需要标准化并存储在关系型数据库中(如 SQL Server 或 Mysql)。如下是标准化数据 alarm 表:

 

startTime

endTime

distance

alarmno

id

2017-06-27 19:00:00

2017-06-27 19:00:00

0

-1

653

2017-07-5 16:03:00

2017-07-5 16:02:00

60

2

654

2017-07-5 18:06:00

2017-07-5 18:03:00

180

4

655

2017-07-5 18:21:00

2017-07-5 18:20:00

60

5

656

2017-07-5 18:39:00

2017-07-5 18:32:00

420

10

657


2017-07-5 18:50:00


11

658

各字段意义如下:

startTime:停机开始时刻(对应原始数据中的 time 字段)

endTime:停机结束时刻(即风机起动时刻,对应原始数据中的 time 字段)

distance:停起间隔秒数(endTime -startTime)

alarmno:停机状态(对应原始数据中的 AV)      

id:记录编号(自增型)

另外还有风机编号等不重要的字段,这里略去。

 

原始数据和标准化数据对应关系举例:

1. 连续的多个(至少一个)停机数据,简称为停机区;连续的多个(至少 1 个)起机数据,简称为起机区。原始数据一定是两区交替出现的形式,比如下图黑色为起机区,红色为停机区。

DynamicData: time:2017-07-05   15:06:00.000,AV:1.0,AS:0

DynamicData: time:2017-07-05   15:35:00.000,AV:1.0,AS:0

DynamicData:   time:2017-07-05 15:38:00.000,AV:4.0,AS:0

DynamicData:   time:2017-07-05 15:44:00.000,AV:2.0,AS:0

DynamicData:   time:2017-07-05 15:44:01.000,AV:2.0,AS:-32768

DynamicData: time:2017-07-05   15:53:00.000,AV:1.0,AS:0

DynamicData: time:2017-07-05 15:54:00.000,AV:1.0,AS:0

DynamicData:   time:2017-07-05 15:59:00.000,AV:5.0,AS:0

DynamicData: time:2017-07-05   15:59:01.000,AV:5.0,AS:-32768

DynamicData: time:2017-07-05 15:59:02.000,AV:1.0,AS:0

 

2. 停机区和紧挨着的下一个起机区,合称为停起区。如下图框中就是停起区

DynamicData:   time:2017-07-05 15:06:00.000,AV:1.0,AS:0

DynamicData:   time:2017-07-05 15:35:00.000,AV:1.0,AS:0

DynamicData: time:2017-07-05 15:38:00.000,AV:4.0,AS:0

DynamicData: time:2017-07-05 15:44:00.000,AV:2.0,AS:0

DynamicData: time:2017-07-05 15:44:01.000,AV:2.0,AS:-32768

DynamicData: time:2017-07-05     15:53:00.000,AV:1.0,AS:0

DynamicData: time:2017-07-05 15:54:00.000,AV:1.0,AS:0

DynamicData: time:2017-07-05 15:59:00.000,AV:5.0,AS:0

DynamicData: time:2017-07-05 15:59:01.000,AV:5.0,AS:-32768

DynamicData: time:2017-07-05 15:59:02.000,AV:1.0,AS:0

 

3. 显然,停起区对应一条标准化数据,其中停机开始时刻为停机区的第一条,停机结束时刻(起动时刻)为起机区的第一条,这两条数据称为停起对。如下图打 * 号所示:

DynamicData:   time:2017-07-05 15:06:00.000,AV:1.0,AS:0

DynamicData:   time:2017-07-05 15:35:00.000,AV:1.0,AS:0

*DynamicData: time:2017-07-05 15:38:00.000,AV:4.0,AS:0

DynamicData: time:2017-07-05 15:44:00.000,AV:2.0,AS:0

DynamicData: time:2017-07-05 15:44:01.000,AV:2.0,AS:-32768

*DynamicData: time:2017-07-05     15:53:00.000,AV:1.0,AS:0

DynamicData: time:2017-07-05 15:54:00.000,AV:1.0,AS:0

*DynamicData: time:2017-07-05 15:59:00.000,AV:5.0,AS:0

DynamicData: time:2017-07-05 15:59:01.000,AV:5.0,AS:-32768

*DynamicData: time:2017-07-05 15:59:02.000,AV:1.0,AS:0

所以上图中原始数据,只对应 2 条标准化数据(停起对)

startTime

endTime

alarmno

2017-07-05 15:38:00

2017-07-05 15:53:00

4.0

2017-07-05 15:59:00

 2017-07-05 15:59:02

5.0

 

标准化过程

思路:每小时定时访问麦杰库,取出一小时之内的原始数据(以下称为麦杰数据集),转为标准化数据,追加到关系型数据库。

算法 1:从麦杰库取出字符串,转为结构化二维表,以便进行后续计算。这个算法可称为结构化。

算法 2:如果关系型数据库为空,则新增一条初始数据,时间为 8 天前。

算法 3:当麦杰数据集第一条数据为停机时,如果库中最新记录为半条(只有停机说明数据错误),则比对数据后更新库,如果库中最新记录为整条(说明数据正常),则追加数据。

算法 4:当麦杰数据集第一条数据为起机时,需要追溯上一个小时或 N 个小时,直到找到对应的停机时刻,形成完整的停起对。这个算法称为追溯。

算法 5:追溯得到的停起对,需要和数据库最新记录比较,如果是漏写的数据,则追加这条数据。本算法称为补漏。

算法 6:不能无限制追溯,如果追溯 7 天仍然找不到停机时刻,则将 7 天前作为停机时刻。

算法 7:计算出所有的停起对。

算法 8:写库时,如果麦杰数据集以起为结尾,说明最后一个停起对是完整的,但如果麦杰数据集以停结尾,则说明最后一个停起对不完整(风机停了但一直没启动),此时需要向数据库插入半条记录(只有停),待下个小时再访问麦杰时,再补上这条记录的起机时刻。

算法 9:alarm 表旨在记录每次停机停了多久,但业务上有个要求是“两次停机间隔如果小于 10 分钟,则合并为一次停机”,这就要求把两条记录合并,把上一条记录的停机时刻作为合并后的停机时刻,把下一条记录的起机时刻作为合并后的起机时刻。本算法会将多条记录合并为少量记录,可形象的称为“拉链”算法。

 

实现这些计算的难点在于麦杰接口是 OPC,只能用 API 调用,访问起来很不方便,计算起来更是困难重重,因为它不像关系型数据库可用 SQL 语句实现查询、排序、聚合,它只能用 JAVA/C# 从底层实现,代码量极大。

解决方案

我们使用集算器进行优化,最终开发时间缩短了 16 倍。下面来看一下集算器的解决之道。

集算器提供多种数据源接口

除关系数据库,集算器还提供了大量非关系型数据源接口,而且还可以通过调用 JAVA 程序进行扩展。在本例中,通过调用麦杰的 OPC 接口程序读取数据,然后在集算器中进行计算。

代码:

invoke(GetMaiJie.getData,name,string(lasthour),string(nowhour),0,1)

集算器复杂计算能力

集算器提供了丰富的集合运算方法,可以较为方便地完成各类复杂运算。

 

读取并结构化数据:

..

 

追溯(算法 4)、停起对(算法 7)、拉链(算法 9)都是很难实现的算法。以计算停起对为例,JAVA 要 195 行代码,涉及大量嵌套循环以及难以理解的临时变量和条件关系,而集算器只需三行。

 

麦杰数据集如下:

..

先将麦杰数据集分组,每组是一个停起区,代码:

A14=麦杰数据集.group@i(AV!=1.0 && AV[-1]==1.0)

..

再将组内数据继续分组,即将每个起停区分为起机区与停机区,代码:

B14=A14.(~.group@i(AV[-1]!=1.0 && AV==1.0))

..

最后取出每个停机区和起机区的第一条,形成停起对。代码:

停起对

=B14.new(~(1)(1).time:eTime,if(~.len()>1,~(2)(1).time,null):bTime,~(1)(1).AV:AV)

..

可以看到,集算器代码精炼易懂,可以直观表达算法逻辑,IDE 下还可以断点调试,而同样的算法数据库也难以实现。

避免频繁写库

由于拉链算法会频繁操作关系数据库,每次还要计算停起间隔,其他算法也会反复查询数据库,这就对数据库造成巨大压力,稳定性也会变差。算法中之所以易发生漏写数据、数据错误,也是因为频繁写库造成的。

集算器有统一入库口径,可在算法中实现数据比较、拉链、查询等操作,最后将原始数据和更改后的数据自动比对,批量生成 SQL,只更新一次数据库。通过统一入库口径,数据库压力显著减轻,执行效率大大提升,原本 JAVA 中的漏写数据、数据错误不再发生。

 

代码:

A4=conn=connect("mysql")

A21=conn.update@l(改后表: 原始表,alarm)

A22=conn.close()

原始表:
imagepng

改后表:
imagepng

不存在计算误差

之前用 JAVA 实现追溯算法,由于缺乏直观易用的结构化库函数,只能用两个嵌套循环分别实现,一个用来计算麦杰数据集的停起对,向下循环;一个用来计算上麦杰的停起对,向上循环。向下循环比较直观,但向上循环过于抽象复杂,导致无法算出上麦杰的起机时间,只能用麦杰数据集的第一条(一定是起机时间)代替。这两个起机时间虽然属于同一个起机区,时间相差较小,但毕竟存在隐患,如果进行整月整年的统计,就会出现较大误差。

集算器内置高性能结构化函数,上麦杰和麦杰数据集可合并后一起计算,不必分成两种算法,因此不存在这种隐患。

JAVA 实现中类似的情况有很多,基本都是由于 JAVA 等高级语言缺少集合计算类库,实现过于复杂导致。

 

集算器完整代码:

..

方案效果

这里对比一下使用集算器前后的差异。

指标

JAVA

SPL(集算器)

提升

代码量

2029 行

22 行

92 倍

工作量

49 人天

3 人天

16.3

维护性

较差

易管理、热切换

总结

在成熟报表工具的支持下,报表格式开发的工作量已经不大,工作量已经从呈现阶段转到数据准备阶段了(本例仅给出报表数据准备算法,省略了报表的制作过程),这部分开发量占比远远大于报表布局那些事。尤其需要对接特殊数据源且涉及复杂计算时,报表工具往往无能为力。

集算器的出现很好地解决了这些问题。集算器提供了丰富的数据源接口(可扩展)和计算类库可以满足各类复杂计算的需要,过程化脚本编辑使得算法实现也更简单,从而进一步提升报表开发效率。

除了开发效率,在性能和应用结构方面集算器也有很大优势。对集算器感兴趣可以再参考 敏捷数据计算引擎,在SPL COOK中还有大量集算器敏捷计算的例子。

 

其它相关案例:

我们怎样把 A 石油集团油井分析报表开发周期缩短 10+ 倍
我们怎样把 B 石化集团多维度多层级叉乘统计报表开发周期缩短 120 倍
我们怎样把 C 银行绩效考核报表开发周期缩短 5 倍
我们怎样把 D 制造企业库龄计算报表开发周期缩短 7.5 倍