一个夏天的下午,负责大数据分析的算法工程师小伍来找我,问:“我精心研究了一个车辆载荷分析算法,其中用到了那个'基于#@$%%^$的模型',原理是这样的,@##$%^#$%^#@$#%@#。我在笔记本上用Python写了个脚本试了一下,感觉还不错,但是我想把这些算法和DWF上管理的车辆信息管理的界面对接起来,用户选择一个车辆,然后点击分析,后端启动我的脚本同时传入车号的信息,脚本运行好以后把分析结果写回到DWF的数据库里,一刷新就把结果展示出来,怎么办呢?哦,对了,如果可能的话,可以把我的Python脚本也管理起来,让以后我只要选择算法就更好了”。
我看了看,说:“不难,你需要的是在后端写点脚本就可以完成这个任务了。利用下面的几个DWF提供的调用即可实现目标:”
这个后端脚本的函数,可以直接启动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脚本的参数,在上面的例子中:
对应的python脚本可以这样编写,详细的python语法这里就不解释了。
import sys ... def main(): if len(sys.argv) > 1 : oid = sys.argv[1] ... |
“OK,那我怎么拿到DWF中管理的文件路径呢?”,说到这里,小伍问到。
好问题,这件事情不难,DWF在后端是已经准备好了接口实现这个目标,如果你在DWF实体类或者关联类上绑定了LocalFile类型的属性,那么文件可以作为附件上传到后端,然后就用下面的两个函数。
这个函数传入一个对象的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]即可访问到数据文件的路径。
有了这个函数以后,还可以多做一步,这样可以配置任务执行的使用选择什么分析模型,具体的做法是这样的:
在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 }`); } |
如果希望将结果输出并且保存到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的临时文件 let resultFile = File.createTempFile(oid, 'tmp'); this.sh.execute(`python ${scriptPath} ${oid} ${dataPath} ${restulPath}`); this.omf.setLocalFile(oid, "AnalyticTask", "resultFile", resultFile); // JDK在退出的时候删除临时文件 resultFile.deleteOnExit() } |
“嗯,还不错,这样我可以去搭建一个原型系统了,等等!可能还是有问题!”,小伍眼睛一转,紧接着问到:
”如果甲方爸爸要我直接对接到他们的数据库上怎么办呢?我记得在他们的生产环境里,车辆的原始传感器数据好像是存在IoTDB里的,然后,我分析出来的结果也得写到DWF的PG关系数据库里,那这怎么办呢?“,只见小伍嘴角一斜,丢出一句话:
”他们不是说:‘不以解决实际问题为目标的理论上的炫技,都是耍流氓嘛...’”。
OK,这就涉及到生产环境的问题了,为了不成为甲方眼里“耍流氓”的人,你可以这么做:
假设,分析任务上面包含车辆的全局唯一标识,名字为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项目就是一个这样的系统。
图:分析模型和分析任务的关系
可以通过数据模型包直接导入将上述模型导入到空白的DWF之中。
说话之间,已经到了傍晚,只见万道霞光照进办公室,小伍看了看窗外,感觉信心十足,答到:“哦,原来是这样的,好!我这就回去试试!”。
“别急!”,我说:“先开完组会再说,马上就要开始了。”,只听熟悉的声音响起,全组开始汇报大家开始汇报工作,晚上继续工作。