Nifi之ExecuteScript使用方法 (一)

本文是一系列文章中的第一篇,描述了如何使用ExecuteScript完成某些任务的各种Recipe

本章介绍了如何使用NiFi处理器ExecuteScript完成某些任务的各种Recipe,以及Groovy,Jython,Javascript(Nashorn)和JRuby中给出的示例。本系列文章中的内容包括:

第1部分 - NiFi API和FlowFiles简介

  • 从传入队列获取流文件
  • 创建新的流文件
  • 使用流文件属性
  • 传输流文件
  • 记录

第2部分- FlowFile I / O和错误处理

  • 从流文件中读取
  • 写入流文件
  • 读取和写入流文件
  • 错误处理

第3部分- 高级功能

  • 使用动态属性
  • 添加模块
  • 国家管理
  • 访问控制器服务

 

Introduce

ExecuteScript是一个多功能处理器,允许用户使用编程语言编写自定义逻辑,每次触发ExecuteScript处理器时都会执行该编程语言。为脚本提供以下变量绑定以启用对NiFi组件的访问:

session:这是对分配给处理器的ProcessSession的引用.该会话允许您对流文件执行操作,如create(),putAttribute()和transfer(),以及read()和write().

context:这是对处理器的ProcessContext的引用。它可用于检索处理器属性,关系,Controller Services和StateManager。

log:这是对处理器的ComponentLog的引用。使用它将消息记录到NiFi,例如log.info('Hello World')

REL_SUCCESS:这是对为处理器定义的“成功”关系的引用。它也可以通过引用父类的静态成员(ExecuteScript)来继承,但是某些引擎(如Lua)不允许引用静态成员,因此这是一个便利变量。它还节省了必须使用关系的完全限定名称。

REL_FAILURE:这是对为处理器定义的“失败”关系的引用.与REL_SUCCESS一样,它也可以通过引用父类的静态成员(ExecuteScript)来继承.

动态属性:ExecuteScript中定义的任何动态属性都将作为设置为与动态属性对应的PropertyValue对象的变量传递给脚本引擎。这允许您获取属性的String值,还可以根据NiFi表达式语言评估属性,将值转换为适当的数据类型(例如布尔值等)等。因为动态属性名称变为脚本的变量名,您必须知道所选脚本引擎的变量命名属性。例如,Groovy不允许在变量名中使用句点(.),因此如果“my.property”是动态属性名称,则会发生错误。

与这些变量的交互是通过NiFi Java API完成的,下面的每个配方都将讨论引入的相关API调用.以下部分中的配方对流文件执行各种功能,例如读/写属性,转移到关系,记录等.请注意,示例是片段并且不按原样运行.例如,如果已使用session.get()从队列中检索流文件,则必须将其传输到关系或删除,否则将发生错误.这些片段应该简洁明了,只能说明所提出的概念,而不需要添加样板代码来使它们成为可行的例子.在后面的文章中,我将把它们放在一起,以显示执行有用任务的完整工作脚本.

 Recipes 

Recipe1:从会话中获取传入的流文件.

使用案例:当我们使用ExecuteScript时,往往是希望对一个连续的数据流进行处理.首先,我们要从数据流的队列中获取到一条数据.

方法:使用会话对象中的get()方法.此方法返回要处理的下一个最高优先级的FlowFile.如果没有要处理的FlowFile,则该方法将返回null,请注意,即使FlowFiles有稳定的流入处理器,也可能返回null,如果处理器有多个并发任务,并且其他任务已经检索到FlowFiles,则会发生这种情况,如果脚本需要FlowFile继续处理,那么如果从session.get()返回null,它应立即执行return.

示例如下

  Groovy

flowFile = session.get()
if(!flowFile) return

  Jython

flowFile = session.get() 
if (flowFile != None):
    # All processing code starts at this indent
# implicit return at the end

  此处需要说明,博主自己在使用ExecuteScript Processor的时候,脚本语言选用的是Python,但是在Processor实际执行的时候,是执行的Jython.也就是说,当你选择python作为脚本驱动语言,那么请时刻记住,你在写Jython代码,类库的引用要格外注意.其中影响最大的一点就是,Nifi使用Jython作为脚本驱动器,我们在Execute Processor中只能引入纯python(.py)的模块,类似于pandas和numpy的本机编译模块将引入失败.导入方法后边再详细讨论.

  Javascript

var flowFile = session.get();
if (flowFile != null) {
   // All processing code goes here
}

  JRuby

flowFile = session.get()
if flowFile != nil
   # All processing code goes here
end

不管使用上边列举的哪种语言,通过执行提供的样例代码,就可以得到流中的单条数据.此时我们可以对数据进行我们想要的转化.

 

Recipe2:从会话中获取多个传入流文件

使用案例: 您有到ExecuteScript的传入连接,并希望从队列中一次检索多条数据进行处理.

方法:使用会话对象中的get(maxResults)方法.此方法从工作队列返回maxResults FlowFiles.如果没有可用的FlowFiles,则返回一个空列表(该方法不返回null).注意:如果存在多个传入队列,则根据是否在单个调用中轮询所有队列或仅轮询单个队列,但笔者同时提出,在Nifi1.1.0+和其之前的版本,所有的提取多条数据都是在单个队列中提取的而不是轮循.此BUG已修复:NIFI-2751

  Groovy

flowFileList = session.get(100)
if(!flowFileList.isEmpty()) {
   flowFileList.each { flowFile -> 
       // Process each FlowFile here
   }
}

  Jython

flowFileList = session.get(100)
if not flowFileList.isEmpty():
    for flowFile in flowFileList: 
         # Process each FlowFile here

JavaScript

flowFileList = session.get(100)
if(!flowFileList.isEmpty()) {
  for each (var flowFile in flowFileList) { 
       // Process each FlowFile here
  }
}

  JRuby

flowFileList = session.get(100)
if !(flowFileList.isEmpty())
   flowFileList.each { |flowFile| 
       # Process each FlowFile here
   }
end

 

Recipe3 : 创建一个新的FlowFile

使用案例 : 对于ExecuteScript处理完毕的数据,我们会希望生成一个新的FlowFile或发送出去存储或发送到下一个处理器继续后续处理.

  Groovy

flowFile = session.create()
// Additional processing here

  Jython

flowFile = session.create() 
# Additional processing here

  JavaScript

var flowFile = session.create();
// Additional processing here

  JRuby

flowFile = session.create()
# Additional processing here

 

Recipe4 : 根据传入的FlowFile创建一个新的FlowFile

使用案例: FlowFile是可以设置属性(标签)的.当前一个ExecuteScript中对特定的数据设置了特定的标签,下一个ExecuteScript想延用上一次的标签时,通过此方法创建.新创建的FlowFile将继承除UUID之外的所有父属性,此方法将自动生成Provenance FORK事件或Provenance JOIN事件,具体取决于在提交ProcessSession之前是否从同一父级生成其他FlowFiles.

  Groovy

flowFile = session.get()
if(!flowFile) return
newFlowFile = session.create(flowFile)
// Additional processing here

Jython

flowFile = session.get() 
if (flowFile != None):
    newFlowFile = session.create(flowFile) 
    # Additional processing here

  JavaScript

var flowFile = session.get();
if (flowFile != null) {
  var newFlowFile = session.create(flowFile);
  // Additional processing here
}

  JRuby

flowFile = session.get()
if flowFile != nil
  newFlowFile = session.create(flowFile)
  # Additional processing here
end

 

Recipe5 : 给FlowFile添加属性(单个)

使用案例: 当想对特定的流数据添加自定义属性时

方法:使用会话对象中的putAllAttributes(flowFile,attributeMap)方法.此方法使用给定Map中的键/值对更新给定的FlowFile属性.注意:"uuid"属性对于FlowFile是固定的,不能修改;;如果属性名为"uuid",则将被忽略.

这是值得一提的FlowFile对象是不可变的;这意味着如果您通过API更新FlowFile的属性(或以其他方式更改)您将获得一个新的指向新FlowFile对象的引用,这一点十分重要.您必须保留对FlowFile的最新版本的引用,所有对FlowFile的操作,比如传输,增加属性等,每次操作的都必须是基于最新版本的FlowFile,否则执行时将出错.

通常,用于存储FlowFile引用的变量将被从更改FlowFile的方法返回的最新版本覆盖(中间FlowFile引用将被自动丢弃).在这些示例中,您将看到这种在添加属性时重用flowFile.请注意,FlowFile的当前引用将作为putAttribute()方的返回值法.生成的FlowFile有一个名为“myAttr”的属性,其值为“myValue”.另外,该方法采用String作为值, 如果你有一个Object,你必须将它序列化为一个String.

若想一次为FlowFile添加多个属性,请参考下节.

  Groovy

flowFile = session.get()
if(!flowFile) return
flowFile = session.putAttribute(flowFile, 'myAttr', 'myValue')

  Jython

flowFile = session.get() 
if (flowFile != None):
    flowFile = session.putAttribute(flowFile, 'myAttr', 'myValue')
# implicit return at the end

  JavaScript

var flowFile = session.get();
if (flowFile != null) {
   flowFile = session.putAttribute(flowFile, 'myAttr', 'myValue')
}

  JRuby

flowFile = session.get()
if flowFile != nil
   flowFile = session.putAttribute(flowFile, 'myAttr', 'myValue')
end

 

Recipe6 : 给FlowFile添加多个属性

使用案例 : 同上一小节

方法 : 使用会话对象中的putAllAttributes(flowFile,attributeMap)方法.此方法使用给定Map中的键/值对更新给定的FlowFile属性.注意:"uuid"属性对于FlowFile是固定的,不能修改;;如果属性名为"uuid",则将被忽略.

这里有一个小技巧是,创建一个Map将所有想要设置的attribute存入Map中,调用putAllAttributes()方法传入Map即可,这样做要比每个属性的键/值对都调用一次putAttribute()更有效.因为每次调用FlowFile的方法,都会生成一个FlowFile的临时版本.调用越多,则存储临时版本所需要的代价就越大.

  Groovy

attrMap = ['myAttr1': '1', 'myAttr2': Integer.toString(2)]
flowFile = session.get()
if(!flowFile) return
flowFile = session.putAllAttributes(flowFile, attrMap)

  Jython

attrMap = {'myAttr1':'1', 'myAttr2':str(2)}
flowFile = session.get() 
if (flowFile != None):
    flowFile = session.putAllAttributes(flowFile, attrMap)
# implicit return at the end

  JavaScript

var number2 = 2;
var attrMap = {'myAttr1':'1', 'myAttr2': number2.toString()}
var flowFile = session.get() 
if (flowFile != null) {
    flowFile = session.putAllAttributes(flowFile, attrMap)
}

  JRuby

attrMap = {'myAttr1' => '1', 'myAttr2' => 2.to_s}
flowFile = session.get() 
if flowFile != nil
    flowFile = session.putAllAttributes(flowFile, attrMap)
end

 

Recipe7 : 从流文件中获取单个属性

使用案例 : 需要在ExecuteScript中获取流数据的属性.

方法 : 使用FlowFile对象中的getAttribute(attributeKey)方法.此方法返回给定attributeKey的String值,如果未找到attributeKey,则返回null.这些示例显示了"filename"属性值的检索.

  Groovy

flowFile = session.get()
if(!flowFile) return
myAttr = flowFile.getAttribute('filename')

  Jython

flowFile = session.get() 
if (flowFile != None):
    myAttr = flowFile.getAttribute('filename')
# implicit return at the end

  JavaScript

var flowFile = session.get() 
if (flowFile != null) {
    var myAttr = flowFile.getAttribute('filename')
}

 JRuby 

flowFile = session.get() 
if flowFile != nil
    myAttr = flowFile.getAttribute('filename')
end

 

Recipe8 : 从流文件中获取所有属性

使用案例 : 同上一节

方法 : 使用FlowFile对象中的getAttributes()方法.此方法返回带有String键和String值的Map,表示流文件的属性的键/值对.这些示例显示了对FlowFile的所有属性的Map的迭代.

  Groovy

flowFile = session.get()
if(!flowFile) return
flowFile.getAttributes().each { key,value ->
  // Do something with the key/value pair
}

 Jython

flowFile = session.get() 
if (flowFile != None):
    for key,value in flowFile.getAttributes().iteritems():
       # Do something with key and/or value
# implicit return at the end

  JavaScript

var flowFile = session.get() 
if (flowFile != null) {
    var attrs = flowFile.getAttributes();
    for each (var attrKey in attrs.keySet()) { 
       // Do something with attrKey (the key) and/or attrs[attrKey] (the value)
  }
}

  JRuby

flowFile = session.get() 
if flowFile != nil
    flowFile.getAttributes().each { |key,value| 
       # Do something with key and/or value
   }
end

 

 

Recipe9 : 将流文件传输到relationship

使用案例 : 当在ExecuteScript中处理完流数据后,我们需要将处理成功的数据发送到其他Processor,将处理失败的流数据写入失败队列.

方法使用会话对象中的transfer(flowFile,relationship)方法.在Nifi的文档中描述:此方法根据给定的关系将给定的FlowFile传输到适当的目标处理器工作队列,如果关系导致多个目标,则复制FlowFile的状态,使得每个目标都接收FlowFile的精确副本,尽管每个目标都具有其自己的唯一标识.

注意:ExecuteScript将在每次执行结束时执行session.commit()以确保已提交操作.您不需要(也不应该)在脚本中执行session.commit().

  Groovy

flowFile = session.get()
if(!flowFile) return
// Processing occurs here
if(errorOccurred) {
  session.transfer(flowFile, REL_FAILURE)
}
else {
  session.transfer(flowFile, REL_SUCCESS)
}

  Jython

flowFile = session.get() 
if (flowFile != None):
    # All processing code starts at this indent
    if errorOccurred:
        session.transfer(flowFile, REL_FAILURE)
    else:
        session.transfer(flowFile, REL_SUCCESS)
# implicit return at the end

  JavaScript

var flowFile = session.get();
if (flowFile != null) {
   // All processing code goes here
   if(errorOccurred) {
     session.transfer(flowFile, REL_FAILURE)
   }
   else {
     session.transfer(flowFile, REL_SUCCESS)
   }
}

  JRuby

flowFile = session.get()
if flowFile != nil
   # All processing code goes here
   if errorOccurred
     session.transfer(flowFile, REL_FAILURE)
   else
     session.transfer(flowFile, REL_SUCCESS)
   end
end

 

Recipe10 : 以指定的日志记录级别向日志发送消息

使用案例 : 您希望将处理期间发生的某些事件报告给日志记录框架,写入到Nifi的日志中.

方法 : 将log变量与warn(),trace(),debug(),info()或error()方法一起使用.这些方法可以使用单个String,或者后跟对象数组的String,或者后跟对象数组后跟Throwable的String.第一个用于简单消息.第二个用于当您有一些要记录的动态对象/值时.要在记录内容中使用字符串,请在消息中使用"{}".这些是按照外观的顺序针对Object数组进行评估的,因此如果消息显示为"Found these things:{} {} {}"并且Object数组为['Hello',1,true],则记录的消息将为"Found these things : Hello 1 True".日志记录方法的第三种形式还采用了一个可抛出的参数,当捕捉到异常并希望对其进行日志记录时,该参数非常有用.

  Groovy

log.info('Found these things: {} {} {}', ['Hello',1,true] as Object[])

  Jython

from java.lang import Object
from jarray import array
objArray = ['Hello',1,True]
javaArray = array(objArray, Object)
log.info('Found these things: {} {} {}', javaArray)

  JavaScript

var ObjectArrayType = Java.type("java.lang.Object[]");
var objArray = new ObjectArrayType(3);
objArray[0] = 'Hello';
objArray[1] = 1;
objArray[2] = true;
log.info('Found these things: {} {} {}', objArray)

  JRuby

log.info('Found these things: {} {} {}', ['Hello',1,true].to_java)

 

希望这些片段有助于在各种脚本语言和流文件操作的上下文中说明NiFi API的各个部分.在本系列的下一篇文章中,我将讨论读取和写入流文件的内容,以及讨论错误处理技术.

 

以上为Nifi  ExecuteScript的基本使用方法.感谢Matt Burgess的分享也感谢您的阅读.

原文地址:https://community.hortonworks.com/articles/75032/executescript-cookbook-part-1.html

你可能感兴趣的:(Nifi)