页面树结构
转至元数据结尾
转至元数据起始

问题

一个夏天的下午,负责大数据分析的算法工程师小伍来找我,问:“我精心研究了一个车辆载荷分析算法,其中用到了那个'基于#@$%%^$的模型',原理是这样的,@##$%^#$%^#@$#%@#。我在笔记本上用Python写了个脚本试了一下,感觉还不错,但是我想把这些算法和DWF上管理的车辆信息管理的界面对接起来,用户选择一个车辆,然后点击分析,后端启动我的脚本同时传入车号的信息,脚本运行好以后把分析结果写回到DWF的数据库里,一刷新就把结果展示出来,怎么办呢?哦,对了,如果可能的话,可以把我的Python脚本也管理起来,让以后我只要选择算法就更好了”。

解法

我看了看,说:“不难,你需要的是在后端写点脚本就可以完成这个任务了。利用下面的几个DWF提供的调用即可实现目标:”

启动Python脚本并传参

this.sh.execute

这个后端脚本的函数,可以直接启动python脚本,参数为一个字符串,将完整的python脚本路径输入后即可启动执行,例如,假设服务器上有一个名为analytic.py的脚本在/home/scripts路径下,那么调用可以这么写:

let scriptPath = "home/script/analytic.py";
this.sh.execute(`python ${scriptPath}`);

如果,希望像python脚本中传入一些参数,例如:当前正在处理对象的全局唯一标识和数据文件所在位置,那么可以这样写:

let scriptPath = "home/script/analytic.py";
let oid = this.obj.oid;
this.sh.execute(`python ${scriptPath} ${oid}`);

这时候,在python里通过sys.argv参数可以得到DWF传递给python脚本的参数,在上面的例子中:

  • argv[0]取值为:"home/script/analytic.py"
  • argv[1]取值为:对象的oid

对应的python脚本可以这样编写,详细的python语法这里就不解释了。

import sys
...
def main():
    if len(sys.argv) > 1 :
        oid = sys.argv[1]
...

以文件为基础输入和输出数据

“OK,那我怎么拿到DWF中管理的文件路径呢?”,说到这里,小伍问到。

好问题,这件事情不难,DWF在后端是已经准备好了接口实现这个目标,如果你在DWF实体类或者关联类上绑定了LocalFile类型的属性,那么文件可以作为附件上传到后端,然后就用下面的两个函数。

this.omf.getFilePath

这个函数传入一个对象的oid,DWF中类的英文名以及LocalFile类型属性的属性英文名名,如果在后端的能找到文件,那么DWF会把这个文件的名字以字符串的方式返回,启动python脚本的时候作为启动参数传递进去即可。

例如:有一个分析任务类(AnalyticTask)上有一个分析文件属性(dataFile),需要被analytic.py脚本进行处理,那么可以这么写:

let scriptPath = "home/script/analytic.py";
let oid = this.obj.oid;
let dataPath = this.omf.getFilePath(oid, "AnalyticTask", "dataFile");

// 要检查一下文件是否存在
let Files = Java.type('java.nio.file.Files');
let Paths = Java.type('java.nio.file.Paths');
if (Files.exist(Paths.get(dataPath)){
	this.sh.execute(`python ${scriptPath} ${oid} ${dataPath }`); 
}

上面这段代码里使用了一些JDK里面自带的文件处理函数,以便检查文件是否存在,当这个脚本在后端被执行了以后,在Python里通过sys.argv[2]即可访问到数据文件的路径。

有了这个函数以后,还可以多做一步,这样可以配置任务执行的使用选择什么分析模型,具体的做法是这样的:

  • 设计一个AnalyticModel类,上面有一个modelFile属性用来保存Python脚本的py文件。
  • 在AnalyticTask类上增加一个modelOid的属性指向这个分析模型,并且允许用户编辑。

在AnalyticTask类的表单上绑定一个操作调用这个脚本,上面的脚本就可以写成这样:

let scriptPath = this.omf.getFilePath(this.obj.modelOid, "AnalyicModel","modelFile");
let oid = this.obj.oid;
let dataPath = this.omf.getFilePath(oid, "AnalyticTask", "dataFile"); 

// 要检查一下文件是否存在
let Files = Java.type('java.nio.file.Files');
let Paths = Java.type('java.nio.file.Paths');
if (Files.exist(Paths.get(dataPath)){
	this.sh.execute(`python ${scriptPath} ${oid} ${dataPath }`); 
}

this.omf.setFilePath

如果希望将结果输出并且保存到DWF里,那么可以在启动python脚本的时候输入一个目标输出文件,将这个临时文件的路径也作为脚本的启动参数传入,由Python脚本输出以后在调用DWF后端的this.omf.setLocalFile即可将结果文件附加到指定的属性上。

一般来说,可以利用JDK提供的临时文件生成机制,创建一个临时文件,在python脚本执行完毕后,再将临时文件绑定到指定的对象上,假设上面提到的AnalytTask类上面有一个属性名字为resultFile,那么可以这么编写DWF后端脚本:

let scriptPath = "home/script/analytic.py";
let oid = this.obj.oid;
let dataPath = this.omf.getFilePath(oid, "AnalyticTask", "dataFile"); //假设dataFile属性为一个绝对路径

// 要检查一下文件是否存在
let Files = Java.type('java.nio.file.Files');
let Paths = Java.type('java.nio.file.Paths');
let File = Java.type('java.io.File');
if (Files.exist(Paths.get(dataPath)){
	// 生成一个后缀为tmp的临时文件,linux下默认路径为/tmp
	let resultFile = File.createTempFile(oid, 'tmp');
	this.sh.execute(`python ${scriptPath} ${oid} ${dataPath} ${resultFile}`);
	this.omf.setLocalFile(oid, "AnalyticTask", "resultFile", resultFile);
	// JDK在退出的时候删除临时文件
    resultFile.deleteOnExit()
}

以数据库为基础输入和输出数据

“嗯,还不错,这样我可以去搭建一个原型系统了,等等!可能还是有问题!”,小伍眼睛一转,紧接着问到:

”如果甲方爸爸要我直接对接到他们的数据库上怎么办呢?我记得在他们的生产环境里,车辆的原始传感器数据好像是存在IoTDB里的,然后,我分析出来的结果也得写到DWF的PG关系数据库里,那这怎么办呢?“,只见小伍嘴角一斜,丢出一句话:

”他们不是说:‘不以解决实际问题为目标的理论上的炫技,都是耍流氓嘛...’”。

OK,这就涉及到生产环境的问题了,为了不成为甲方眼里“耍流氓”的人,你可以这么做:

  • 利用Python脚本的参数传递方法将被分析对象的oid传入脚本,这里可能是车辆对象的代号,这可以在分析任务类AnalyticTask上增加一个类似于assetOid属性,指向具体的是某个车辆。
  • 在Python脚本里拿到车辆的全局唯一标识,直接访问关系数据库获得车辆的信息,这其实不难,通过廖雪峰老师的快速入门教程:学习如何使用SQLAlchemy
  • 如果传感器数据存在IoTDB里,就更加不成为问题了,因为,IoTDB是Apache的顶级开源项目,所以,社区里面有很多热心网友已经提供了方案,比如:乔嘉林博士的博客了解如何在Python中连接IoTDB就是一个不错的地方。
  • 分析完以后,只需要将结果回写到DWF的PG数据库里即可,这些结果表可以是DWF直接定制的表,你也可以先写入到PG的一张自定义表然后利用外部实体类映射完成结果展示的工作。

假设,分析任务上面包含车辆的全局唯一标识,名字为assetOid,那么启动的脚本就可以这么写:

let scriptPath = this.omf.getFilePath(this.obj.modelOid, "AnalyicModel","modelFile");
let oid = this.obj.oid;
let assetOid = this.obj.assetOid;

// 要检查一下文件是否存在
let Files = Java.type('java.nio.file.Files');
let Paths = Java.type('java.nio.file.Paths');
if (Files.exist(Paths.get(dataPath)){

	//数据的操作就交给python脚本了
	this.sh.execute(`python ${scriptPath} ${oid} ${assetOid }`); 
}

不过有一点得注意一下,如果数据分析涉及到的数据量很大,时效性要求又很高,那就还是得用一个专门的大数据计算集群完成这个工作,比如:大数据系统软件国家工程实验室的Flok项目就是一个这样的系统。

示例数据模型

  • 车辆信息(Asset):用于记录车辆的基本信息,例如:车号,车型,业主名称等,简化一点这里就提供了一个设备类型和设备名称的属性。
  • 分析模型(AnalyticModel):用于存放车辆分析算法的python脚本。
  • 分析任务(AnalysisTask):每次分析的任务对应的是分析结果记录,其中可以允许用户上传一个数据文件,在正式的生产系统里可以直接把数据对接到某个数据库,例如:IoTDB

图:分析模型和分析任务的关系

可以通过数据模型包直接导入将上述模型导入到空白的DWF之中。

尾声

说话之间,已经到了傍晚,只见霞光照进办公室,小伍看了看窗外,感觉信心十足,答到:“哦,原来是这样的,好!我这就回去试试!”。

“别急!”,我说:“先开完组会再说,马上就要开始了。”,只听熟悉的声音响起,全组开始汇报大家开始汇报工作,晚上继续工作。


  • 无标签