本篇主要是介紹自定義處理器的開發方式及Nifi處理器開發的一些細節
Nifi-Processor自定義開發的流程
之前說過,大部分的資料處理,我們可以基於ExcuseGroovyScript處理器,編寫Groovy指令碼去完成(或者Jpython,Js指令碼等對應的元件),只能說這是基於Nifi平臺的使用層面,還不能算是對於Nifi的開發,真正基於Nifi開發,至少要掌握Nifi擴充套件自定義元件,即根據實際需求,進行內建處理器(Processor)的開發。當然Nifi本身官方是給出了擴充套件的口,如果我們下載Nifi原始碼,就能看到,在Nifi原始碼中有個 nifi-example-bundle 的程式碼包,就是Nifi提供的擴充套件方式,裡面附了一個小樣例,感興趣的可以去看Nifi的原始碼。這裡主要是對開發自定義處理器進行一個完整的經驗總結。
總體來說,Nifi-Processor自定義開發的流程就是:基於Nifi規則下的編碼—>程式碼打包為nar包——>釋出使用。
基於Nifi規則下的編碼
Nifi本身提供了擴充套件處理器的方法,並有一套完整的介面、類,只需要按著規則去實現介面、繼承抽象類、覆蓋方法,實現一個自己的Processor類
程式碼打包為nar包
Nifi內建Processor的存在形式就是nar包(Nifi自身定義的),必須將自定義的程式碼,按著一定的規則進行打包,最終你的Processor是以nar的形式嵌入Nifi中,其實下面的 “自定義Nifi-Processor專案的兩種搭建方式” 主要就是maven專案兩種不同的表現方式,根本目的是為了最終能生成nar包
釋出使用
完成開發後,將打好的nar包,放到Nifi安裝目錄的lib下即可,lib目錄本身存放了Nifi出廠自帶的資料處理器nar包,如果想依樣畫葫蘆的學習,可以隨便摘取一個nar包,反編譯一下看看它的寫法。
自定義Nifi-Processor專案的兩種搭建方式
nifi的專案主要是maven專案,我們必須按照maven規範進行開發Nifi的元件,這裡經過踩坑,博主總結了兩種Nifi處理器開發的方式:
- 基於Nifi官方提供的bundle模式
- 基於普通maven專案,通過pom檔案的方式完成nar包的構建
不管哪種方式,自定義Nifi-Processor元件專案根本的步驟就是:
- 在resources目錄下建立 META-INF.services目錄,然建立檔案 org.apache.nifi.processor.Processor
- org.apache.nifi.processor.Processor 檔案內宣告自己的Processor類的全路徑
- 將專案配置成能夠打包為nar包的結構
其實這種只是在 “將專案配置成能夠打包為nar包的結構” 上有差異,本質上都是為了打成Nifi推出的nar程式包。總體上來講,第一種更為規範,更符合專案工程化管控;第二種有點野路子的感覺,就是一個獨立的maven專案,但是好理解、操作方便,建議新手可以從第二種方式入手。
基於官方給出的bundle模式的開發
這種方式是官方給出的,特點就是便於管理和控制開發規範,我們從Nifi給出的樣例碼說起;
下載Nifi原始碼後,可以看見 nifi-example-bundle 的程式碼包,其實這一程式碼包就是標準的自定義Processor開發的模板,自定義建立的時候,可以參考該樣例,建立一個maven專案,進行開發。
它本身是基於maven模組化的構建,整體的目錄結構是nifi-example-bundle作為一個整體專案,下屬兩個模組包,nifi-nifi-example-nar和nifi-nifi-example-processors,其中nifi-example-bundle作為一個根專案,nifi-nifi-example-nar裡只有一個pom檔案,負責將程式打包成nar包,nifi-nifi-example-processors才是真正的靈魂,自定義Processor程式碼的存放處。
nifi-example-bundle
整體專案nifi-example-bundle作為一個maven根專案,通過pom檔案管控nifi-nifi-example-nar和nifi-nifi-example-processors,其pom檔案結構為:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.apache.nifi</groupId> <artifactId>nifi-external</artifactId> <version>1.11.4</version> </parent> <artifactId>nifi-example-bundle</artifactId> <packaging>pom</packaging> <modules> <module>nifi-nifi-example-processors</module> <module>nifi-nifi-example-nar</module> </modules> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.nifi</groupId> <artifactId>nifi-nifi-example-processors</artifactId> <version>1.13.2</version> </dependency> </dependencies> </dependencyManagement> </project>
nifi-nifi-example-processors
nifi-nifi-example-processors專案是核心的,也是我們主要寫程式碼的地方,我們要寫的自定義Processor即在這裡完成,
1、在resources目錄下建立 META-INF.services,然建立檔案 org.apache.nifi.processor.Processor
# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. org.apache.nifi.processors.WriteResourceToStream
2、開發自己的Processor程式,建立一個Processor類,並且繼承實現抽象類 AbstractProcessor,根據自己需要實現一些方法
package org.apache.nifi.processors; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.AbstractProcessor; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; import org.apache.nifi.processor.ProcessorInitializationContext; import org.apache.nifi.processor.Relationship; import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.processor.io.OutputStreamCallback; @Tags({ "example", "resources" }) @CapabilityDescription("This example processor loads a resource from the nar and writes it to the FlowFile content") public class WriteResourceToStream extends AbstractProcessor { public static final Relationship REL_SUCCESS = new Relationship.Builder() .name("success") .description("files that were successfully processed").build(); public static final Relationship REL_FAILURE = new Relationship.Builder() .name("failure") .description("files that were not successfully processed").build(); private Set<Relationship> relationships; private String resourceData; @Override protected void init(final ProcessorInitializationContext context) { final Set<Relationship> relationships = new HashSet<Relationship>(); relationships.add(REL_SUCCESS); relationships.add(REL_FAILURE); this.relationships = Collections.unmodifiableSet(relationships); final InputStream resourceStream = getClass() .getClassLoader().getResourceAsStream("file.txt"); try { this.resourceData = IOUtils.toString(resourceStream, Charset.defaultCharset()); } catch (IOException e) { throw new RuntimeException("Unable to load resources", e); } finally { IOUtils.closeQuietly(resourceStream); } } @Override public Set<Relationship> getRelationships() { return this.relationships; } @OnScheduled public void onScheduled(final ProcessContext context) { } @Override public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException { FlowFile flowFile = session.get(); if (flowFile == null) { return; } try { flowFile = session.write(flowFile, new OutputStreamCallback() { @Override public void process(OutputStream out) throws IOException { IOUtils.write(resourceData, out, Charset.defaultCharset()); } }); session.transfer(flowFile, REL_SUCCESS); } catch (ProcessException ex) { getLogger().error("Unable to process", ex); session.transfer(flowFile, REL_FAILURE); } } }
3、nifi-nifi-example-processors的pom檔案樣例:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.apache.nifi</groupId> <artifactId>nifi-example-bundle</artifactId> <version>1.13.2</version> </parent> <artifactId>nifi-nifi-example-processors</artifactId> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.apache.nifi</groupId> <artifactId>nifi-api</artifactId> <version>1.13.2</version> </dependency> <dependency> <groupId>org.apache.nifi</groupId> <artifactId>nifi-processor-utils</artifactId> <version>1.13.2</version> </dependency> <dependency> <groupId>org.apache.nifi</groupId> <artifactId>nifi-mock</artifactId> <version>1.13.2</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.rat</groupId> <artifactId>apache-rat-plugin</artifactId> <configuration> <excludes> <exclude>src/main/resources/file.txt</exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
nifi-nifi-example-nar
這一maven只是負責把Processor的jar整合打包成nar包,只有一個pom檔案
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.apache.nifi</groupId> <artifactId>nifi-example-bundle</artifactId> <version>1.13.2</version> </parent> <artifactId>nifi-example-nar</artifactId> <packaging>nar</packaging> <properties> <maven.javadoc.skip>true</maven.javadoc.skip> <source.skip>true</source.skip> </properties> <dependencies> <dependency> <groupId>org.apache.nifi</groupId> <artifactId>nifi-nifi-example-processors</artifactId> </dependency> </dependencies> </project>
完整的demo程式碼可以去github中檢視 :https://github.com/GCC1566/Apache-Nifi-Processor/tree/master/nifi-JsonProcessor-bundle
獨立的maven程式模式開發
這種方式很獨立,即當作一個maven專案去建立,不需要考慮過多。
按著maven專案的構建方式,構建一個基本的maven專案:
修改pom.xml檔案
這種方式看起來更容易,只是所有的難點都集中在了pom檔案,具體的pom檔案樣例下面給出,可直接複製到自己的專案中,有額外需要的依賴,自行追加。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.gcc.nifi</groupId> <artifactId>JsonDistributeProcessor</artifactId> <version>1.0</version> <packaging>nar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <nifi.version>1.13.2</nifi.version> </properties> <dependencies> <dependency> <groupId>org.apache.nifi</groupId> <artifactId>nifi-api</artifactId> <version>${nifi.version}</version> </dependency> <dependency> <groupId>org.apache.nifi</groupId> <artifactId>nifi-utils</artifactId> <version>${nifi.version}</version> </dependency> <dependency> <groupId>org.apache.nifi</groupId> <artifactId>nifi-processor-utils</artifactId> <version>${nifi.version}</version> </dependency> <dependency> <groupId>org.apache.nifi</groupId> <artifactId>nifi-mock</artifactId> <version>${nifi.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.nifi</groupId> <artifactId>nifi-nar-maven-plugin</artifactId> <version>1.3.1</version> <extensions>true</extensions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.18.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.1.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </build> </project>
在專案裡建立自己的java檔案,實現 AbstractProcessor 類
前置都準備好了,就可以開始編寫自定義的Processor,就隨便建個類,並讓他繼承 AbstractProcessor,然後完成一些方法。
例如:
demo:
package net.gcc.nifi.processors; import org.apache.nifi.annotation.behavior.SideEffectFree; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.expression.ExpressionLanguageScope; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.*; import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.util.StringUtils; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * JSONDistributeProcessor * According to the rules, distribute the data to the specified Relationship * @author GCC */ @SideEffectFree @Tags({"JsonDataDistribute","net.gcc"}) @CapabilityDescription("Divide data according to configuration") public class JsonDataDistributeProcessor extends AbstractProcessor{ public static final Relationship REL_SUCCESS = new Relationship.Builder() .name("success") .description("files that were successfully processed").build(); public static final Relationship REL_FAILURE = new Relationship.Builder() .name("failure") .description("files that were not successfully processed").build(); private Set<Relationship> relationships; @Override protected void init(final ProcessorInitializationContext context) { final Set<Relationship> relationships = new HashSet<Relationship>(); relationships.add(REL_SUCCESS); relationships.add(REL_FAILURE); this.relationships = Collections.unmodifiableSet(relationships); } @Override public Set<Relationship> getRelationships() { return this.relationships; } @OnScheduled public void onScheduled(final ProcessContext context) { } @Override public void onTrigger(final ProcessContext context, //具體的資料處理 } }
在resources目錄下建立 META-INF.services,然建立檔案 org.apache.nifi.processor.Processor
類實現後,將實現類的全路徑寫在Nifi規定的配置檔案種。
org.apache.nifi.processor.Processor檔案的內容樣例(可直接複製,只需要改動最後一行即可):
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
net.gcc.nifi.processors.JsonDataDistributeProcessor
這裡,最後一行為個人自定義Processor元件的類全路徑,一個maven專案中可以建立多個Processor,只需要在這裡追加。
完整的demo程式碼可以去github中檢視 :https://github.com/GCC1566/JsonDistributeProcessor
關於Nifi-Processor開發的一些知識
FlowFile
FlowFile是一種邏輯概念,它將一段資料與一組關於該資料的屬性相關聯。這些屬性包括FlowFile的唯一識別符號,以及其名稱,大小和任何數量的其他特定於流的值。雖然FlowFile的內容和屬性可以更改,但FlowFile物件是不可變的。ProcessSession可以對FlowFile進行修改。
FlowFiles的核心屬性在org.apache.nifi.flowfile.attributes.CoreAttributes
列舉中定義。您將看到的最常見屬性是filename,path和uuid。引號中的字串是CoreAttributes
列舉中屬性的值。
-
Filename(“filename”):FlowFile的檔名。檔名不應包含任何目錄結構。
-
UUID(“uuid”):分配給此FlowFile的通用唯一識別符號,用於區分FlowFile與系統中的其他FlowFiles。
-
Path(“path”):FlowFile的路徑指示FlowFile所屬的相對目錄,不包含檔名。
-
Absolute Path (“absolute.path”):FlowFile的絕對路徑表示FlowFile所屬的絕對目錄,不包含檔名。
-
Priority(“priority”):表示FlowFile優先順序的數值。
-
MIME Type(“mime.type”):此FlowFile的MIME型別。
-
Discard Reason(“discard.reason”):指定丟棄FlowFile的原因。
-
Alternative Identifier(“alternate.identifier”):表示已知引用此FlowFile的FlowFile的UUID以外的識別符號。
ProcessSession
ProcessSession通常簡稱為“會話”,它提供了一種機制,通過該機制可以建立,銷燬,檢查,克隆FlowFiles並將其傳輸到其他處理器。此外,ProcessSession還提供了通過新增或刪除屬性或修改FlowFile內容來建立FlowFiles的修改版本的機制。ProcessSession還公開了一種用於釋出原始碼事件的機制,該機制提供了跟蹤FlowFile的沿襲和歷史的能力。在一個或多個FlowFiles上執行操作後,可以提交或回滾ProcessSession。
ProcessorInitializationContext
建立處理器後,initialize
將使用InitializationContext
物件呼叫其方法。此物件向處理器公開配置,該配置在處理器的整個生命週期內不會更改,例如處理器的唯一識別符號。
ProcessContext
ProcessContext提供了處理器和框架之間的橋樑。它提供有關處理器當前如何配置的資訊,並允許處理器執行特定於Framework的任務,例如產生其資源,以便框架將安排其他處理器執行而不會不必要地消耗資源。
PropertyDescriptor
PropertyDescriptor定義將由Processor,ReportingTask或ControllerService使用的屬性。屬性的定義包括其名稱,屬性的描述,可選的預設值,驗證邏輯,以及關於處理器是否有效所需的屬性的指示符。PropertyDescriptors是通過例項化PropertyDescriptor.Builder
類的例項,呼叫適當的方法來填充有關屬性的詳細資訊,最後呼叫該build
方法來建立的。
Validator(驗證器)
PropertyDescriptor必須指定一個或多個Validator,可用於確保使用者輸入的屬性值有效。如果Validator指示屬性值無效,則在屬性生效之前,將無法執行或使用Component。如果未指定Validator,則假定Component無效,NiFi將報告該屬性不受支援。
Relationship
關係定義FlowFile可以從處理器傳輸到的路由。通過例項化Relationship.Builder
類的例項,呼叫適當的方法來填充關係的細節,最後呼叫 build
方法來建立關係。
ComponentLog
鼓勵處理器通過ComponentLog
介面執行日誌記錄 ,而不是獲取第三方記錄器的直接例項。這是因為通過ComponentLog進行日誌記錄允許框架將超出可配置嚴重性級別的日誌訊息呈現給使用者介面,從而允許在發生重要事件時通知監視資料流的人員。此外,它通過在DEBUG模式下記錄堆疊跟蹤並在日誌訊息中提供處理器的唯一識別符號,為所有處理器提供一致的日誌記錄格式。
Processor的職責和一些基本概念
我們來看一個自定義Processor的繼承結構:
圖中,Tags,SideEffectFree、CapabilityDescription,是基於註解的,都是為了UI頁面上的展示,這裡我們忽略不看,主要看它的主線,整個自定義Processor繼承自AbstractProcessor,而AbstractProcessor又繼承了一系列。這裡說明一下,雖然Processor
是一個可以直接實現的介面,但這樣做非常罕見,因為它org.apache.nifi.processor.AbstractProcessor
是幾乎所有處理器實現的基類。AbstractProcessor
類提供的功能的顯著,這使得開發的處理器更容易,更方便的任務。對於本文件的範圍,我們將主要關注AbstractProcessor
處理Processor API時的類。
處理器AbstractProcessor
處理器有很多方法,關乎著處理器的載入、執行、處理資料等等,這裡只介紹幾個最為重要的。
init()
該方法在處理器初始化的時候被呼叫,該方法採用單個引數,即型別ProcessorInitializationContext
。上下文物件為Processor提供ComponentLog,Processor的唯一識別符號和ControllerServiceLookup,可用於與配置的ControllerServices互動。每個這樣的物件是由AbstractProcessor儲存,並且可以由子類經由獲得getLogger
,getIdentifier
和 getControllerServiceLookup
方法。
getRelationships()
處理器通過覆蓋該getRelationships
方法來公開有效的關係集 。這個方法沒有引數,並返回Set
的Relationship
物件。對於大多數處理器,此Set將是靜態的,但其他處理器將根據使用者配置動態生成Set。對於Set為靜態的那些處理器,建議在Processor的建構函式或init方法中建立一個不可變的Set並返回該值,而不是動態生成Set。這種模式有助於實現更清晰的程式碼和更好的效能。
getSupportedPropertyDescriptors()
大多數處理器在能夠使用之前需要一些使用者配置。處理器支援的屬性通過該getSupportedPropertyDescriptors
方法向頁面的元件公開 。這個方法沒有引數,並返回List
的 PropertyDescriptor
物件。List中物件的順序很重要,因為它決定了在使用者介面中呈現屬性的順序。
PropertyDescriptor
目的是通過建立一個新的例項構造PropertyDescriptor.Builder
物件,呼叫構建器的適當的方法,並最終呼叫build
方法。
雖然此方法涵蓋了大多數用例,但有時需要允許使用者配置名稱未知的其他屬性。這可以通過覆蓋該getSupportedDynamicPropertyDescriptor
方法來實現 。此方法將 String
唯一引數作為引數,該引數指示屬性的名稱。該方法返回一個PropertyDescriptor
物件,該 物件可用於驗證屬性的名稱以及值。應該構建從此方法返回的任何PropertyDescriptor,isDynamic
在PropertyDescriptor.Builder
類中將值設定為true 。AbstractProcessor的預設行為是不允許任何動態建立的屬性。
然後處理器的屬性,是需要驗證的,如果處理器的配置無效,則無法啟動處理器。可以通過在PropertyDescriptor上設定Validator或通過PropertyDescriptor.Builder的allowableValues
方法或identifiesControllerService
方法限制屬性的允許值來驗證Processor屬性。
但是,有時候單獨驗證處理器的屬性是不夠的。為此,AbstractProcessor公開了一個customValidate
方法。該方法採用單個引數型別ValidationContext
。此方法的返回值是描述驗證期間發現的任何問題Collection
的 ValidationResult
物件。只應返回其isValid
方法返回的ValidationResult物件 false
。僅當所有屬性根據其關聯的Validators和Allowable Values有效時,才會呼叫此方法。即,只有當所有屬性本身都有效時才會呼叫此方法,並且此方法允許整體驗證處理器的配置。
onPropertyModified()
這方法總的作用就是:響應配置更改,也就是當頁面元件的配置標籤頁裡配置發生變化,該方法就會被執行一次。當使用者更改Processor的屬性值時,onPropertyModified
將為每個已修改的屬性呼叫該 方法。該方法有三個引數:PropertyDescriptor,它指示修改了哪個屬性,舊值和新值。如果屬性沒有先前的值,則第二個引數將是null
。如果刪除了屬性,則第三個引數將是null
。重要的是要注意,無論值是否有效,都將呼叫此方法。只有在實際修改了值時才會呼叫此方法,而不是在使用者更新處理器而不更改其值時呼叫此方法。在呼叫此方法時,保證呼叫此方法的執行緒是當前在Processor中執行程式碼的唯一執行緒,除非Processor本身建立自己的執行緒。
onTrigger()
當處理器有工作要做時,它計劃onTrigger
通過框架呼叫其方法來完成。該方法有兩個引數:a ProcessContext
和aProcessSession
。該onTrigger
方法的第一步通常是通過呼叫get
ProcessSession上的一個方法來獲取要在其上執行工作的FlowFile 。對於從外部源將資料提取到NiFi的處理器,將跳過此步驟。然後,處理器可以自由檢查FlowFile屬性; 新增,刪除或修改屬性; 讀取或修改FlowFile內容; 並將FlowFiles傳輸到適當的關係。
處理器被觸發時(When Processors are Triggered)
onTrigger
只有在計劃執行處理器並且處理器存在工作時,才會呼叫處理器的方法。如果滿足以下任何條件,則稱處理器存在工作:
-
-
目標為Processor的Connection在其佇列中至少有一個FlowFile
-
處理器沒有傳入的連線
-
處理器使用@TriggerWhenEmpty批註進行批註
-
有幾個因素會導致onTrigger
呼叫Processor的 方法。首先,除非使用者已將處理器配置為執行,否則不會觸發處理器。如果計劃執行處理器,則週期性地(該週期由使用者介面中的使用者配置)檢查處理器是否有工作,如上所述。如果是這樣,框架將檢查處理器的下游目的地。如果處理器的任何出站連線已滿,則預設情況下,將不會安排處理器執行。
但是,@TriggerWhenAnyDestinationAvailable
註釋可以新增到Processor的類中。在這種情況下,需求被更改,以便只有一個下游目標必須“可用”(如果連線的佇列未滿,則目標被視為“可用”),而不是要求所有下游目標都可用。
與處理器排程有關的還有@TriggerSerially
註釋。使用此Annotation的處理器永遠不會有多個執行緒onTrigger
同時執行該方法。但是,必須注意,執行程式碼的執行緒可能會從呼叫更改為呼叫。因此,仍然必須注意確保處理器是執行緒安全的!
自定義Nifi-Processor元件的單元測試
處理器或控制器服務的大多數單元測試都是通過建立TestRunner
類的例項來完成的。
一般需要額外引入maven依賴,由apache-nifi官方提供
<dependency> <groupId>org.apache.nifi</groupId> <artifactId>nifi-mock</artifactId> <version>${nifi version}</version> </dependency>
TestRunner
TestRunner類是Nifi專門用來模擬實際環境下,執行Processor或者ControllerService的一個介面,它的例項可以通過 TestRunner.newTestRunner()來建立,newTestRunner方法的引數是你要執行的Proccessor的類;例如
TestRunner run = TestRunner.newTestRunner(JSONDistributeProcessor.class);
新增ControllerServices
在啟動一個處理器的時候,有些處理器可能需要額外需要ControllerService才能正常執行,這時候,可以通過模擬追加ControllerService來完成。
它的性質可以通過呼叫被設定:
setProperty(ControllerService, PropertyDescriptor, String)
,
setProperty(ControllerService, String, String)
setProperty(ControllerService, PropertyDescriptor, AllowableValue)
任何一個來完成。每種方法都返回一個 ValidationResult
。然後可以檢查此物件以確保通過呼叫該屬性有效isValid
。可以通過呼叫setAnnotationData(ControllerService, String)
方法來設定註釋資料。
assertValid(ControllerService) 方法來模擬驗證ControllerService有效
assertNotValid(ControllerService) 來模擬驗證ControllerService無效
將Controller Service新增到Test Runner並進行配置後,通過呼叫:
enableControllerService(ControllerService)
方法啟用它 。
如果Controller Service無效,則此方法將丟擲IllegalStateException。
設定屬性值
每個處理器可能需要具備一定的屬性配置,也就是Web頁面中每個Processor的配置頁面
這裡可以通過:
setProperty(PropertyDescriptor, String)方法進行追加配置
每個setProperty
方法再次返回一個ValidationResult
屬性,可用於確保屬性值有效。
預製FlowFiles測試資料
然後模擬前置管道湧入的資料,待測資料,可以通過TestRunner的enqueue
方法來模擬批量的FlowFile資料,本身enqueue方法本身支援幾種輸入:byte[] ,InputStram、Path、Map
當然也可以自行實現enqueue方法,來滿足自身的擴充套件需要
執行處理器
配置Controller Services並將必要的FlowFile排入佇列後,可以通過呼叫run
方法來觸發處理器執行TestRunner
。如果在沒有任何引數的情況下呼叫此方法,它將使用@OnScheduled
註釋呼叫Processor中的任何方法,呼叫Processor的onTrigger
方法一次,然後執行@OnUnscheduled
finally @OnStopped
方法。
如果希望在觸發onTrigger
其他事件@OnUnscheduled
和 @OnStopped
生命週期事件之前執行該方法的多次迭代,則該run(int)
方法可用於指定現在onTrigger
應該呼叫的許多迭代。
還有,當我們想要觸發處理器上執行,但不會觸發時間@OnUnscheduled
和@OnStopped
生命週期事件。例如,這有助於在這些事件發生之前檢查處理器的狀態。這可以使用run(int, boolean)
和傳遞false
作為第二個引數來實現。但是,在執行此操作後,呼叫@OnScheduled
生命週期方法可能會導致問題。因此,我們現在可以onTrigger
再次執行,而不會通過使用方法的run(int,boolean,boolean)
版本run
並false
作為第三個引數傳遞來發生這些事件。
如果測試多個執行緒發生的行為很有用,這也可以通過呼叫setThreadCount
方法來實現 TestRunner
。預設值為1個執行緒。如果使用多個執行緒,請務必記住,run
呼叫TestRunner
指定應觸發處理器的次數,而不是每個執行緒應觸發處理器的次數。因此,如果執行緒計數設定為2但 run(1)
被呼叫,則只使用一個執行緒。
驗證輸出Relationship
處理器執行完畢後,單元測試通常需要驗證FlowFiles是否符合預期要求。通過以下兩種方法來檢視:
TestRunners
assertAllFlowFilesTransferred():此方法將關係和整數作為引數,以指示應該將多少FlowFiles傳輸到該關係。除非將此數量的FlowFiles轉移到給定的關係或者任何FlowFile被轉移到任何其他關係,否則該方法將無法通過單元測試。
assertTransferCount():僅驗證FlowFile計數是給定關係的預期數量。
如果想要獲取實際輸出的資料樣例,通過以下方法:
getFlowFilesForRelationship(): 獲得實際的輸出FlowFiles 。這個方法返回一個List<MockFlowFile>
。重要的是要注意List的型別MockFlowFile
,而不是FlowFile
介面。這樣做是因為MockFlowFile
有許多方法可以驗證內容。
一個實際例子
public class JsonDataDistributeProcessorTest { private TestRunner runner = TestRunners.newTestRunner(new JsonDataDistributeProcessor()); @Test public void runProcessor(){ runner.setProperty("zhangsan","{\n" + " \"logic\":\"&&\",\n" + " \"fields\":[\n" + " {\n" + " \"field\":\"name\",\n" + " \"value\":[\"張三\"]\n" + " },\n" + " {\n" + " \"field\":\"age\",\n" + " \"value\":[12]\n" + " }\n" + " ]\n" + "}"); runner.setProperty("lisi","{\n" + " \"logic\":\"&&\",\n" + " \"fields\":[\n" + " {\n" + " \"field\":\"name\",\n" + " \"value\":[\"張三\"]\n" + " }" + " ]\n" + "}"); runner.assertValid(); runner.enqueue("[{\"name\":\"張三\",\"age\":12},\n" + "{\"name\":\"王三\",\"age\":12},\n" + "{\"name\":\"張三\",\"age\":16},\n" + "{\"sdf\":\"wn\",\"age\":18}\n" + "]"); runner.run(); List<MockFlowFile> zhangsanlist = runner.getFlowFilesForRelationship("zhangsan"); List<MockFlowFile> lisi = runner.getFlowFilesForRelationship("lisi"); for(MockFlowFile mk:zhangsanlist) { System.out.println(mk.toString()); } for(MockFlowFile mk:lisi) { System.out.print(mk.toString()); } } }
1、Nifi:基本認識
2、Nifi:基礎用法及頁面常識
3、Nifi:ExcuseXXXScript元件的使用(一)
4、Nifi:ExcuseXXXScript元件的使用(二)
5、Nifi:ExcuseXXXScript元件的使用(三)
6、Nifi:自定義處理器的開發