Nifi初探 - 創建一個簡單的自定義Nifi Processor

網上關於Nifi自定義Processor的中文資料,要麼是很古老的eclipse版本,要麼太過於簡單,學習Nifi的道路確實有點看不清楚,好在找到一篇從零構造一個JsonPROCESSOR的英文文章,單純翻譯外加心得記錄,使用的Nifi版本是1.3.0,希望對大家有幫助。
源地址:http://www.nifi.rocks/developing-a-custom-apache-nifi-processor-json/

1. 開始

Nifi有很多可用的、文檔化的Processor資源,但是某些時候你依然需要去開發屬於你自己的Processor,例如從某些特殊的數據庫中提取數據、提取不常見的文件格式,或者其他特殊情況。這篇文章我們創建了一個基礎的json文件讀取Processor,將內容轉化爲屬性值,本片文章的代碼位於[GitHub](https://github.com/coco11563/jsonNifiExampleProcessor)

2. 項目依賴

目前搭建Nifi Processor項目環境使用較多的還是Maven + IDEA,與其他的組件開發不同,Nifi的要求是在/src/main/resources/META-INF/services/目錄下新建一個文件org.apache.nifi.processor.Processor,這個類似於配置文件,指向該Processor所在的目錄,比如我的配置文件內容就是

sha0w.pub.jsonNifi.processors.JsonProcessor

我的Maven POM文件的配置訊息如下所示,主要的幾個包是

  1. 提供nifi api的nifi-api
  2. 提供基礎工具的nifi-utils
  3. 提供Process抽象類接口的nifi-processor-utils
  4. 測試的nifi-mock以及junit
  5. jsonpath包
  6. common-io

附上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>sha0w.pub</groupId>
    <artifactId>jsonNifi</artifactId>
    <version>1.0-SNAPSHOT</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.3.0</nifi.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.nifi</groupId>
                <artifactId>nifi-nar-maven-plugin</artifactId>
                <version>1.0.0-incubating</version>
                <extensions>true</extensions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.15</version>
            </plugin>
        </plugins>
    </build>

    <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.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <version>1.2.0</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.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

POM內的plugin組件提供了一個將類打包成nifi組件的nar包打包方式(類似於war包),打包部分需要nifi-api依賴,其他組件在之後可以看到對應的作用。

3. JSON Processor

現在自定義Nifi Processor的前期準備工作都做完了,可以開始構建屬於我們自己的Processor了。首先我們在我們之前META-INF中的文件中寫入的地址處創建一個java class(比如說我的java class 就創建在sha0w.pub.jsonNifi.processors/下的 JsonProcessor.java)

@SideEffectFree
@Tags({"JSON","SHA0W.PUB"})
@CapabilityDescription("Fetch value from json path.")
public class JsonProcessor extends AbstractProcessor{
}

我們使其extends AbstractProcessor這個抽象類,@Tag標籤是爲了在web GUI中,能夠使用搜索的方式快速找到我們自己定義的這個Processor。CapabilityDescription內的值會暫時在Processor選擇的那個頁面中,相當於一個備註。
一般來說只需要繼承AbstractProcessor就可以了,但是某些複雜的任務可能需要去繼承更底層的AbstractSessionFactoryProcessor這個抽象類。

private List<PropertyDescriptor> properties;
private Set<Relationship> relationships;

public static final String MATCH_ATTR = "match";

public static final PropertyDescriptor JSON_PATH = new PropertyDescriptor.Builder()
        .name("Json Path")
        .required(true)
        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
        .build();

public static final Relationship SUCCESS = new Relationship.Builder()
        .name("SUCCESS")
        .description("Succes relationship")
        .build();

以上代碼展示的是一些Processor內部變量以及一些初始化的Nifi變量,可見我們通過PropertyDescriptor以及Relationship中的模板方法定義了兩個新的關係和屬性描述值,這些值會出現在webUI中,多個選項型的屬性值定義如下:

public static final AllowableValue EXTENSIVE = new AllowableValue("Extensive", "Extensive",
    "Everything will be logged - use with caution!");
public static final AllowableValue VERBOSE = new AllowableValue("Verbose", "Verbose",
    "Quite a bit of logging will occur");
public static final AllowableValue REGULAR = new AllowableValue("Regular", "Regular",
    "Typical logging will occur");

public static final PropertyDescriptor LOG_LEVEL = new PropertyDescriptor.Builder()
  .name("Amount to Log")
  .description("How much the Processor should log")
  .allowableValues(REGULAR, VERBOSE, EXTENSIVE)
  .defaultValue(REGULAR.getValue())
  ...
  .build();

這樣定義的處理器的WEB UI的LOG_LEVEL選項內就會有三個選項。

@Override
public void init(final ProcessorInitializationContext context){
    List<PropertyDescriptor> properties = new ArrayList<>();
    properties.add(JSON_PATH);
    this.properties = Collections.unmodifiableList(properties);

    Set<Relationship> relationships = new HashSet<>();
    relationships.add(SUCCESS);
    this.relationships = Collections.unmodifiableSet(relationships);
}

@Override
public Set<Relationship> getRelationships(){
    return relationships;
}

@Override
public List<PropertyDescriptor> getSupportedPropertyDescriptors(){
    return properties;
}

如上是初始化Nifi進程,由於Nifi是高度併發條件,所以在此處進行更改時需要很小心,同時這也是爲什麼propertiesrelationship是存儲在一個不可變的集合中。

 @Override
    public void onTrigger(ProcessContext processContext, ProcessSession processSession) throws ProcessException {
        final AtomicReference<String> value = new AtomicReference<>();

        FlowFile flowFile = processSession.get();

        processSession.read(flowFile, in -> {
            try{
                String json = IOUtils.toString(in);
                String result = JsonPath.read(json, "$.hello");
                value.set(result);
            }catch(Exception ex){
                ex.printStackTrace();
                getLogger().error("Failed to read json string.");
            }
        });

        String results = value.get();
        if(results != null && !results.isEmpty()){
            flowFile = processSession.putAttribute(flowFile, "match", results);
        }

        // To write the results back out ot flow file
        flowFile = processSession.write(flowFile, out -> out.write(value.get().getBytes()));

        processSession.transfer(flowFile, SUCCESS);

    }

然後是整個Processor的核心部分 -> onTrigger 部分, onTrigger方法會在一個flow file被傳入處理器時調用。爲了讀取以及改變傳遞來的FlowFile,Nifi提供了三個callback接口方法:

  • InputStreamCallback:
    該接口繼承細節如下:
session.read(flowfile, new InputStreamCallback() {
    @Override
    public void process(InputStream in) throws IOException {
        try{
            String json = IOUtils.toString(in);
            String result = JsonPath.read(json, "$.hello");
            value.set(result);
        }catch(Exception ex){
            ex.printStackTrace();
            getLogger().error("Failed to read json string.");
        }
    }
});  

使用了Apache Commons內的方法去讀取輸入流,並將其轉換爲String形式,使用JsonPath包去讀取Json數據同時將值傳遞回來
- OutputStreamCallback

flowfile = session.write(flowfile, new OutputStreamCallback() {
    @Override
    public void process(OutputStream out) throws IOException {
        out.write(value.get().getBytes());
    }
});

可見這部分我們只是單純把之前的讀取內容寫到值中
- StreamCallback

flowfile = session.write(flowfile, new OutputStreamCallback() {

    @Override
     public void process(OutputStream out) throws IOException {
        out.write(value.get().getBytes());
    }
});

StreamCallback同時是同一個FlowFile的讀取以及寫入流
然後下面是將讀取json數據寫入到flowFile中。

// Write the results to an attribute
String results = value.get();
if(results != null && !results.isEmpty()){
    flowfile = session.putAttribute(flowfile, "match", results);
}

最後使用transfer()功能傳遞迴這個flowFile以及成功標識。

最後使用mvn clean install功能去build出這個nar包

4. 部署

  1. Copy the target/examples-1.0-SNAPSHOT.nar to $NIFI_HOME/lib
  2. $NIFI_HOME/bin/nifi.sh stop
  3. $NIFI_HOME/bin/nifi.sh start
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章