ETL最佳实践-NiFi

大数据 专栏收录该内容
37 篇文章 0 订阅

NIFI 介绍

概述

简而言之,NiFi旨在自动执行系统之间的数据流。尽管“数据流”一词在多种情况下使用,但我们在此使用它来表示系统之间的自动化和托管信息流。自从企业拥有多个系统以来,就一直存在这个问题空间,其中一些系统创建了数据,而某些系统使用了数据。出现的问题和解决方案已被广泛讨论和阐明。在企业集成模式 中找到了一种全面且易于使用的形式。
这个设计模型,也类似于[seda],提供了许多有益的结果,帮助NiFi成为构建强大和可伸缩数据流的非常有效的平台。这些好处包括: 有利于处理器有向图的可视化创建和管理 本质上是异步的,允许非常高的吞吐量和自然缓冲,即使处理和流量波动 提供了一个高度并发的模型,开发人员不必担心并发的典型复杂性 促进内聚和松散耦合组件的开发,这些组件可以在其他上下文中重用,并促进可测试单元的开发 资源受限的连接使得诸如背压和压力释放等关键功能非常自然和直观 错误处理变得像快乐路径一样自然,而不是粗粒度的包罗万象 数据进入和退出系统的点以及它是如何通过的都很容易理解和跟踪

Apache NiFi 是一个易于使用、功能强大而且可靠的数据拉取、数据处理和分发系统,用于自动化管理系统间的数据流。
它支持高度可配置的指示图的数据路由、转换和系统中介逻辑,支持从多种数据源动态拉取数据。
NiFi原来是NSA(National Security Agency [美国国家安全局])的一个项目,目前已经代码开源,是Apache基金会的顶级项目之一
NiFi基于Web方式工作,后台在服务器上进行调度。
用户可以为数据处理定义为一个流程,然后进行处理,后台具有数据处理引擎、任务调度等组件。
NiFi 核心概念
Nifi 的设计理念接近于基于流的编程 Flow Based Programming。
FlowFile:表示通过系统移动的每个对象,包含数据流的基本属性
FlowFile Processor(处理器):负责实际对数据流执行工作
Connection(连接线):负责不同处理器之间的连接,是数据的有界缓冲区
Flow Controller(流量控制器):管理进程使用的线程及其分配
Process Group(过程组):进程组是一组特定的进程及其连接,允许组合其他组件创建新组件

NIFI 特性

可视化命令与控制
设计,控制,反馈和监测之间的无缝体验
高度可配置
损失容忍vs保证交付
低延迟vs高吞吐量
动态优先
流可以在运行时修改
数据回压
数据溯源
从头到尾跟踪数据流
为可扩展而设计
建立自己的处理器和更多
快速开发和有效的测试
安全
SSL,SSH,HTTPS,加密内容等
多租户授权和内部授权/策略管理

nifi是将数据转换成一种流的形式在各种处理器之间进行处理转换的etl工具,它通过可视化可操作的用户界面来编辑数据,更加直观有效。

kettle 是C/S 架构 ,NiFi是基于WEB的B/S架构,方便集成。

NIFI的优点

可视化的UI界面,各个模块组件之间高度可配置,且每个流程都有监控,可以通过界面直观的看到各个数据处理模块之间的数据流转情况,分析出程序性能瓶颈。
数据流可以在UI界面自由拖拽和拓展,各模块之间相互独立,互不影响。
可以在处理耗时的地方创建多个处理模块并行执行,提升处理速度。类似于代码中加入了多线程,但相对于修改代码,界面配置操作十分简单。
修改方便,任意模块都可以在数据流转过程中随时启停,任意处理模块都可以实现热插拔。数据流流向随时可变。
NiFi的对处理模块有对应的retry机制和错误分发机制,且可配置性强。
NiFi基于组件的热插拔部署,方便集成自定义组件
NiFi支持缓冲所有排队的数据,以及在这些队列达到指定限制时提供背压的能力,或者在数据达到指定年龄(其值已经消失)时使数据老化
具有多种现有组件可以提供数据抽取转换流程
NiFi 可以进行集群部署,横向扩展,提高系统吞吐量

NIFI的缺点

各个步骤中间结果落地导致磁盘IO成为Nifi的瓶颈,这个缺点在数据冗余量越大的时候表现的越明显。
在实现特定业务场景现有组件不能满足或实现复杂,需自定义开发组件

架构

在这里插入图片描述NIFI集群NiFi主要功能概述本部分提供了20,000英尺的NiFi基础知识视图,以便您可以了解Apache NiFi的概况以及一些最有趣的功能。关键功能类别包括流管理,易用性,安全性,可扩展的体系结构和灵活的缩放模型。流管理保证交货NiFi的核心理念是,即使规模很大,也必须保证交付。这可以通过有效使用专用的持久性预写日志和内容存储库来实现。它们的共同设计方式允许很高的事务处理速率,有效的负载分散,写时复制以及发挥传统磁盘读/写的优势。带反压和泄压的数据缓冲NiFi支持缓冲所有排队的数据,并能够在这些队列达到指定的限制时提供反压力,或者在达到指定的使用期限(其值消失)时使数据过期。优先排队NiFi允许设置一种或多种优先级分配方案,以用于如何从队列中检索数据。默认值是最旧的优先,但是有时应该将数据拉到最新的优先,最大的优先或其他一些自定义方案。特定于流的QoS(延迟v吞吐量,丢失容限等)在数据流的某些点上,数据是绝对关键的并且不容忍丢失。在某些情况下,有时必须在几秒钟内将其处理并交付以具有任何价值。NiFi可以实现这些问题的细粒度流特定配置。使用方便视觉命令与控制数据流可能变得非常复杂。能够可视化这些流程并以视觉方式表达它们可以极大地降低复杂性并确定需要简化的区域。NiFi不仅可以直观地建立数据流,而且可以实时建立数据流。与其“设计和部署”,不如说它是模制粘土。如果您对数据流进行更改,则该更改将立即生效。更改是细粒度的,并且与受影响的组件隔离。您无需停止整个流程或一组流程即可进行某些特定的修改。流模板数据流往往是高度面向模式的,尽管通常有许多不同的方法可以解决问题,但能够共享这些最佳实践对它很有帮助。模板使主题专家可以构建和发布其流程设计,并让其他人从中受益并进行协作。资料来源当对象流过系统时,即使在扇入,扇出,转换等过程中,NiFi也会自动记录,索引并提供出处数据。在支持合规性,故障排除,优化和其他方案时,此信息变得至关重要。恢复/记录细粒度历史记录的滚动缓冲区NiFi的内容存储库旨在充当历史的滚动缓冲区。数据仅在内容存储库中老化或需要空间时才被删除。这与数据源功能相结合,为在对象生命周期中甚至跨越几代生命周期的特定点上实现单击内容,下载内容和重播提供了极为有用的基础。安全系统对系统数据流仅是安全的。数据流中每个点的NiFi均通过使用带有加密协议的协议(例如2路SSL)提供安全交换。另外,NiFi使流能够加密和解密内容,并在发送方/接收方的任一侧使用共享密钥或其他机制。用户到系统NiFi启用2-Way SSL身份验证并提供可插入授权,以便它可以在特定级别(只读,数据流管理器,管理)适当地控制用户的访问。如果用户在流中输入诸如密码之类的敏感属性,则会立即在服务器端对其进行加密,即使以加密形式也不会再在客户端公开。多租户授权给定数据流的权限级别适用于每个组件,从而允许admin用户具有细粒度的访问控制级别。这意味着每个NiFi集群都能够处理一个或多个组织的需求。与隔离的拓扑相比,多租户授权为数据流管理提供了一种自助服务模型,使每个团队或组织都可以在完全了解他们无法访问的其余流的情况下管理流。可扩展架构延期NiFi是为扩展而构建的核心,因此,它是一个平台,数据流流程可以在该平台上以可预测和可重复的方式执行和交互。扩展点包括:处理器,控制器服务,报告任务,优先级划分程序和客户用户界面。类加载器隔离对于任何基于组件的系统,依赖关系问题都可能很快发生。NiFi通过提供自定义类加载器模型来解决此问题,确保每个扩展捆绑包都暴露于非常有限的依赖项集合中。结果,可以很少考虑扩展是否会与另一个扩展冲突。这些扩展束的概念称为“ NiFi存档”,并在《开发人员指南》中进行了详细讨论。站点间通信协议NiFi实例之间的首选通信协议是NiFi站点到站点(S2S)协议。通过S2S,可以轻松,高效,安全地将数据从一个NiFi实例传输到另一个实例。NiFi客户端库可以轻松构建并捆绑到其他应用程序或设备中,以通过S2S与NiFi通信。S2S支持基于套接字的协议和HTTP(S)协议作为基础传输协议,从而可以将代理服务器嵌入到S2S通信中。弹性缩放模型横向扩展(聚类)如上所述,NiFi旨在通过将多个节点群集在一起来进行横向扩展。如果设置了一个节点并将其配置为每秒处理数百MB,则可以将适度的群集配置为每秒处理GB。这就带来了有趣的挑战,即在NiFi和从其获取数据的系统之间进行负载平衡和故障转移。使用基于异步排队的协议(例如消息传递服务,Kafka等)可以有所帮助。使用NiFi的“站点到站点”功能也非常有效,因为它是一种协议,它允许NiFi和客户端(包括另一个NiFi群集)相互交谈,共享有关加载的信息以及交换有关特定授权的数据端口。放大和缩小NiFi还设计为以非常灵活的方式放大和缩小。从NiFi框架的角度来看,在增加吞吐量方面,可以在配置时在“调度”选项卡下增加处理器上的并发任务数。这允许更多进程同时执行,从而提供更大的吞吐量。另一方面,您可以将NiFi完美缩小,以适合在由于硬件资源有限而需要占用空间小的边缘设备上运行。为了专门解决首英里数据收集挑战和边缘用例,您可以在此处找到更多详细信息:https : //cwiki.apache.org/confluence/display/NIFI/MiNiFi关于Apache NiFi的子项目MiMiFi(发音为“最小化”,[min-uh-fahy])。

部署

单机部署 NIFI

上传Apache NIFI包到Linux上,解压安装包;或者将你的本地作为服务器,直接解压zip包。

在解压的目录下,找到conf目录,编辑bootstrap.conf文件,修改NIFI的内存配置,默认的值比较小,比如这里我改成启动2g,最大10g
java.arg.2=-Xms2g
java.arg.3=-Xmx10g

在解压的目录下,找到bin目录,可以看到里面有一些脚本
dump-nifi.bat
nifi-env.bat
nifi-env.sh
nifi.sh
run-nifi.bat
status-nifi.bat

在解压的目录下,找到conf目录,编辑nifi.properties文件,修改端口号,默认为8080
nifi.web.http.port=8080
Linux或者Mac,使用nifi.sh start启动NIFI,nifi.sh stop停止NIFI,nifi.sh restart重启NIFI。
Windows下,直接双击run-nifi.bat即可,退出的时候关闭运行窗口就可以了。

集群部署 NIFI

NiFi采用Zero-Master Clustering范例。集群中的每个节点对数据执行相同的任务,但每个节点都在不同的数据集上运行。其中一个节点自动选择(通过Apache ZooKeeper)作为集群协调器。然后,群集中的所有节点都会向此节点发送心跳/状态信息,并且此节点负责断开在一段时间内未报告任何心跳状态的节点。此外,当新节点选择加入群集时,新节点必须首先连接到当前选定的群集协调器,以获取最新流。如果群集协调器确定允许该节点加入(基于其配置的防火墙文件),则将当前流提供给该节点,并且该节点能够加入群集,假设节点的流副本与群集协调器提供的副本匹配。如果节点的流配置版本与群集协调器的版本不同,则该节点将不会加入群集。

zookeeper:NIFI内置zookeeper

编辑实例中,conf/nifi.properties文件,不同节点改成对应内容,内容如下:

nifi.state.management.configuration.file=./conf/state-management.xml
nifi.state.management.provider.local=local-provider
nifi.state.management.provider.cluster=zk-provider
#  指定此NiFi实例是否应运行嵌入式ZooKeeper服务器,默认是false
nifi.state.management.embedded.zookeeper.start=true
nifi.state.management.embedded.zookeeper.properties=./conf/zookeeper.properties

# 3个节点分别是8081 8082 8083
nifi.web.http.port=8081

# 如果实例是群集中的节点,请将此设置为true。默认值为false
nifi.cluster.is.node=true
# 3个节点分别是9081 9082 9083
nifi.cluster.node.protocol.port=9081

# 3个节点分别是6341 6342 6343
nifi.cluster.load.balance.port=6341

# 连接到Apache ZooKeeper所需的连接字符串。这是一个以逗号分隔的hostname:port对列表
nifi.zookeeper.connect.string=localhost:2181,localhost:2182,localhost:2183

修改zookeeper.properties

# 3个节点都一样
server.1=localhost:2111:3111;2181
server.2=localhost:2222:3222;2182
server.3=localhost:2333:3333;2183

修改state-management.xml(3个节点都一样)

<cluster-provider>
        <id>zk-provider</id>
        <class>org.apache.nifi.controller.state.providers.zookeeper.ZooKeeperStateProvider</class>
        <property name="Connect String">localhost:2181,localhost:2182,localhost:2183</property>
        <property name="Root Node">/nifi</property>
        <property name="Session Timeout">10 seconds</property>
        <property name="Access Control">Open</property>
</cluster-provider>

在3个节点的NIFI目录下(bin目录同级),新建state/zookeeper,zookeeper文件夹里新建文件myid,3个节点分别写入1,2,3
#3个节点分别写入 1 2 3
echo 1 > myid
1
2
分别启动所有节点

数据同步(表字段相同)

整体流程如下:
GenerateTableFetch --> ExecuteSQLRecord --> PutDatabaseRecord --> LogAttribute
GenerateTableFetch组件:从源表中生成获取行的“页”的SQL select查询。分区大小属性以及表的行数决定页面和生成的流文件的大小和数量。此外,可以通过设置最大值列来实现增量获取,这将导致处理器跟踪列的最大值,从而只获取那些列的值超过观察到的最大值的行
ExecuteSQLRecord组件:执行提供的SQL选择查询。查询结果将转换为所指定格式输出。使用流,因此支持任意大的结果集。
PutDatabaseRecord组件:使用指定的记录器从传入流文件输入(可能是多个)记录。这些记录被转换为SQL语句,并作为单个批处理执行
连接池配置DBCPConnectionPool

数据同步(表字段不相同)

整体流程如下:
QueryDatabaseTable --> ConvertAvroToJSON --> SplitJson --> EvaluateJsonPath --> ReplaceText --> PutSQL
QueryDatabaseTable组件:生成一个SQL select查询,或使用提供的语句,并执行它来获取指定最大值列中值大于之前看到的最大值的所有行。查询结果将转换为Avro格式。
ConvertAvroToJSON组件:将二进制Avro记录转换为JSON对象。这个处理器提供了Avro字段到JSON字段的直接映射,这样得到的JSON将具有与Avro文档相同的层次结构
SplitJson组件:对于由JsonPath表达式指定的数组元素,将一个JSON文件拆分为多个单独的流文件。每个生成的FlowFile由指定数组的一个元素组成,并传输到关系“split”,同时将原始文件传输到关系“original”。如果没有找到指定的JsonPath,或者没有对数组元素求值,原始文件将被路由到“failure”,并且不生成任何文件。
EvaluateJsonPath组件:根据FlowFile的内容评估一个或多个JsonPath表达式。这些表达式的结果将分配给FlowFile属性,或者写入FlowFile本身的内容,具体取决于处理器的配置。
ReplaceText组件:通过对正则表达式(regex)求值并将与正则表达式匹配的内容部分替换为其他值,更新流文件的内容。通过替换成目标表字段的sql语句,数据可以从EvaluateJsonPath组件存放到的attribute属性中获取,获取方式${key},将替换后的sql语句传递到下游PutSql组件中。
PutSQL组件:执行SQL UPDATE或INSERT命令。传入流文件的内容应该是要执行的SQL命令。
以上两种数据同步都是基于mysql 到 mysql ,oracle只需要更换数据库连接池配置。
注意:oracle数据同步使用EvaluateJsonPath组件获取属性值时字段名称需要大写

NIFI 组件之间数据传递时通过队列的方式控制,因此不能控制事务,但如果有一个组件初始化失败时,上游传递下来的队列中的数据是不会被消费,当组件异常修复之后会继续执行队列中的内容。

binlog日志采集数据同步

为了不影响业务,可以通过binlog日志对数据库表数据进行同步
整体流程:
CaptureChangeMySQL --> RouteOnAttribute --> JoltTransformJSON --> EvaluateJsonPath --> ReplaceText --> PutSQL --> LogAttribute
CaptureChangeMySQL组件:从MySQL数据库中检索更改数据捕获(CDC)事件。CDC事件包括插入、更新、删除操作。事件输出为按操作发生时的顺序排列的单个流文件。读取binlog日志路由下游处理
redis存储客户端配置server服务端
此时你会发现多了一个RedisConnectionPoolService
继续配置RedisConnectionPoolService
​最后启动redis服务端和客户端
RouteOnAttribute 组件:根据binlog中含有的类型参数,把binlog记录的日志操作根据类型进行路由处理,提供给不同的下游分支操作
Routing Strategy:路由策略用默认的Route toProperty name,根据属性名进行路由.添加的自定义属性可以根据业务分发给不同的下游处理器。
JoltTransformJSON组件:对flowfile JSON有效负载应用一系列的Jolt规范。使用转换后的内容创建一个新的FlowFile,并将其路由到“success”关系。如果JSON转换失败,原始的流文件将被路由到“failure”关系。

点击高级设置会打开如下图Jolt测试界面
上面有红叉子的那个区域Jolt Specification是填写我们的Jolt语句的;
左下方区域JSON Input是输入要被处理前的Json数据.
右下方区域JSON Output是输出Input被jolt语句处理后的结果.

Jolt Specification区域输入以下内容

[{
	"operation": "shift",
	"spec": {
		"columns": {
			"*": {
				"@(value)": "@(1,name)"
			}
		}
	}
}]

“operation”: “shift”:实现整理出key,value格式
“operation”: “modify-default-beta”:实现拼接了一个带前缀字符串的新字段apid,以及value是字符串ap_拼接id的value.

JSON Input输入以下内容

{
  "type" : "delete",
  "timestamp" : 1592386594000,
  "binlog_filename" : "mysql-bin.000001",
  "binlog_position" : 229,
  "database" : "ipaas",
  "table_name" : "target",
  "table_id" : 33,
  "columns" : [ {
    "id" : 1,
    "name" : "id",
    "column_type" : -5,
    "value" : 50
  }, {
    "id" : 2,
    "name" : "username",
    "column_type" : 12,
    "value" : "徐朝"
  }, {
    "id" : 3,
    "name" : "userage",
    "column_type" : 4,
    "value" : 20
  }, {
    "id" : 4,
    "name" : "time",
    "column_type" : 12,
    "value" : "2020-06-17 10:31:45"
  } ]
}

最后点击TRANSFORM按钮查看效果
测试没问题,可以复制我们调试好的Jolt Specification内容,返回刚才Jolt Specification这里,贴进去保存配置
EvaluateJsonPath组件:根据FlowFile的内容评估一个或多个JsonPath表达式。这些表达式的结果将分配给FlowFile属性,或者写入FlowFile本身的内容,具体取决于处理器的配置。
ReplaceText组件:通过对正则表达式(regex)求值并将与正则表达式匹配的内容部分替换为其他值,更新流文件的内容。通过替换成目标表字段的sql语句,数据可以从EvaluateJsonPath组件存放到的attribute属性中获取,获取方式${key},将替换后的sql语句传递到下游PutSql组件中。
PutSQL组件:执行上游传递下来的sql语句
LogAttribute组件:记录执行日志

输出结果:

多表分别查询汇总入库(表字段不相同)

完整流程:
同**数据同步(表字段不同)**分别有多条处理流程将数据查询出来,然后使用funnel组件进行数据合并后统一入库

根据规则字段映射

完整流程:
从源数据表中查询出所有数据转换为json,然后通过SplitJson切分成多个json对象,在通过EvaluateJsonPath组件将值存放到属性列表中,再通过ExecuteSQL组件根据字段映射条件查询规则表并转换为json,再通过EvaluateJsonPath组件将规则表数据也添加到源表数据的属性列表中,再根据RouteOnAttribute组件判断条件路由需要的数据到下游;然后通过ReplaceText组件从属性列表中获取值拼接sql交由下游处理器PutSQL执行。

ExecuteSQL组件配置如下:
RouteOnAttribute组件配置如下:
自定义添加过滤条件

自定义组件Nifi Processor

  1. 创建Maven工程
    父工程my-processor,子工程nifi-my-processor-nar和nifi-my-processor-processors,这里使用的版本时1.11.4
    my-processor 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>
    <packaging>pom</packaging>

    <parent>
        <groupId>org.apache.nifi</groupId>
        <artifactId>nifi</artifactId>
        <version>1.11.4</version>
    </parent>

    <groupId>org.apache.nifi</groupId>
    <artifactId>my-processor</artifactId>
    <version>1.11.4</version>

    <name>my-processor</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <modules>
        <module>nifi-my-processor-nar</module>
        <module>nifi-my-processor-processors</module>
    </modules>

</project>

nifi-my-processor-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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>my-processor</artifactId>
        <groupId>org.apache.nifi</groupId>
        <version>1.11.4</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>nifi-my-processor-nar</artifactId>
    <packaging>nar</packaging>

    <name>nifi-my-processor-nar</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <maven.javadoc.skip>true</maven.javadoc.skip>
        <source.skip>true</source.skip>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.nifi</groupId>
            <artifactId>nifi-standard-services-api-nar</artifactId>
            <version>1.11.4</version>
            <type>nar</type>
        </dependency>
        <dependency>
            <groupId>org.apache.nifi</groupId>
            <artifactId>nifi-my-processor-processors</artifactId>
            <version>1.11.4</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

nifi-my-processor-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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>my-processor</artifactId>
        <groupId>org.apache.nifi</groupId>
        <version>1.11.4</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>nifi-my-processor-processors</artifactId>
    <packaging>jar</packaging>

    <name>nifi-my-processor-processors</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>


    <dependencies>
        <dependency>
            <groupId>org.apache.nifi</groupId>
            <artifactId>nifi-api</artifactId>
            <version>1.11.4</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.nifi</groupId>
            <artifactId>nifi-utils</artifactId>
            <version>1.11.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.nifi</groupId>
            <artifactId>nifi-mock</artifactId>
            <version>1.11.4</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
  1. 修改项目编写代码
    删除nifi-my-processor-processors子项目中,src/test中的测试文件(打包可能出现错误)

Nifi的要求是在/src/main/resources/META-INF/services/目录下新建一个文件org.apache.nifi.processor.Processor,这个类似于配置文件,指向该Processor所在的目录,比如我的配置文件内容就是

org.apache.nifi.processor.MyProcessor
代码编写,创建MyProcessor类。其中有设置状态,属性,及处理方法(onTrigger)等

package org.apache.nifi.processor;

import org.apache.nifi.annotation.behavior.ReadsAttribute;
import org.apache.nifi.annotation.behavior.ReadsAttributes;
import org.apache.nifi.annotation.behavior.WritesAttribute;
import org.apache.nifi.annotation.behavior.WritesAttributes;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.SeeAlso;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.annotation.lifecycle.OnScheduled;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;

import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @Classname MyProcessor
 * @Description
 * @Author xuzhaoa
 * @Date 2020/7/2 9:49
 */
@Tags({"example"})
@CapabilityDescription("Provide a description")
@SeeAlso({})
@ReadsAttributes({@ReadsAttribute(attribute = "", description = "")})
@WritesAttributes({@WritesAttribute(attribute = "", description = "")})
public class MyProcessor extends AbstractProcessor {

    public static final PropertyDescriptor MY_PROPERTY = new PropertyDescriptor
            .Builder().name("MY_PROPERTY")
            .displayName("My property")
            .description("Example Property")
            .required(true)
            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
            .build();

    public static final Relationship MY_RELATIONSHIP_SUCCESS = new Relationship.Builder()
            .name("sucess")
            .description("Example relationship Success")
            .build();

    public static final Relationship MY_RELATIONSHIP_FAILURE = new Relationship.Builder()
            .name("failure")
            .description("Example relationship Failure")
            .build();

    private List<PropertyDescriptor> descriptors;

    private Set<Relationship> relationships;

    @Override
    protected void init(final ProcessorInitializationContext context) {
        final List<PropertyDescriptor> descriptors = new ArrayList<PropertyDescriptor>();
        descriptors.add(MY_PROPERTY);
        this.descriptors = Collections.unmodifiableList(descriptors);

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

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

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

    @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;
        }
        // TODO implement
        final AtomicReference<String> value = new AtomicReference<>();
        session.read(flowFile, in -> {
            try {
                StringWriter sw = new StringWriter();
                InputStreamReader inr = new InputStreamReader(in);
                char[] buffer = new char[1024];
                int n = 0;
                while (-1 != (n = inr.read(buffer))) {
                    sw.write(buffer, 0, n);
                }
                String str = sw.toString();

                String result = "处理了:" + str + context.getProperty("MY_PROPERTY").getValue();
                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 = session.putAttribute(flowFile, "match", results);
        }

        flowFile = session.write(flowFile, out -> out.write(value.get().getBytes()));

        session.transfer(flowFile, MY_RELATIONSHIP_SUCCESS);

    }
}

我们使其extends AbstractProcessor这个抽象类,@Tag标签是为了在web GUI中,能够使用搜索的方式快速找到我们自己定义的这个Processor。CapabilityDescription内的值会暂时在Processor选择的那个页面中,相当于一个备注。
一般来说只需要继承AbstractProcessor就可以了,但是某些复杂的任务可能需要去继承更底层的AbstractSessionFactoryProcessor这个抽象类。

我们通过PropertyDescriptor以及Relationship中的模板方法定义了两个新的关系和属性描述值,这些值会出现在webUI中

该组件只是简单的测试将流中数据替换,功能实现主要通过该类自行实现

整个Processor的核心部分 -> onTrigger 部分, onTrigger方法会在一个flow file被传入处理器时调用。为了读取以及改变传递来的FlowFile,Nifi提供了三个callback接口方法

InputStreamCallback:
该接口继承细节如下: 将流中的数据读取处理进行替换
OutputStreamCallback :将内容写入值中
最后使用transfer()功能传递回这个flowFile以及成功标识。

  1. 打包部署
    项目打包后将nifi-my-processor-nar工程target目录中的 nifi-my-processor-nar-1.0-SNAPSHOT.nar 文件,拷贝到 nifi\lib 目录中
    新建流程使用自定义组件
    GenerateFlowFile --> MyProcessor --> PutFile
    GenerateFlowFile 组件配置生成内容
    MyProcessor 组件替换内容

参考资料

http://nifi.apache.org/docs/nifi-docs/

FAQ

MiNiFi和NiFi有什么区别?
MiNiFi是用于从远程位置的传感器和设备上收集数据子集的代理。目的是帮助进行数据的“第一英里收集”,并获取尽可能接近其来源的数据。

这些设备可以是服务器、工作站和便携式计算机,也可以是传感器、自动驾驶汽车、工厂中的机器等,您希望在其中使用MiNiFi中的某些NiFi功能来收集特定数据。在将数据发送到目的地之前,可以对其进行过滤、选择和分类。MiNiFi的目标是使用Edge Flow Manager大规模管理整个流程,以便运营或IT团队可以部署不同的流程定义并根据业务需要收集任何数据。以下是一些需要考虑的细节:

NiFi被设计为通常位于数据中心或云中的中央位置,以在已知的外部系统(如数据库、对象存储等)中移动数据或从中收集数据。NiFi应该被视为将数据移回的网关在异构环境或混合云体系结构中来回切换。
MiNiFi在主机上本地运行,进行一些计算和逻辑运算,并且仅将您关心的数据发送到外部系统以进行数据分发。这样的系统当然可以是NiFi,但也可以是MQTT代理、云提供商服务等。MiNiFi还支持一些用例,在这些用例中,网络带宽可能受到限制,需要减少通过网络发送的数据量。
MiNiFi代理有两个版本:C ++和Java。MiNiFi C ++选项的占用空间非常小(几MB的内存,很少的CPU),但是可用的处理器却更少。MiNiFi Java选项是轻量级的NiFi单节点实例,是NiFi的无头版本,他没有用户界面也没有集群功能。尽管如此,它仍要求Java在主机上可用。
如果可以使用Kafka作为群集的入口点,为什么还要使用NiFi?
这是一个很好的问题,许多参加我的Live NiFi Demo Jam的人都问了这个问题。您可以通过以下方式确定何时使用NiFi和何时使用Kafka。

Kafka设计用于主要针对较小文件的面向流的用例,然而摄取大文件不是一个好主意。NiFi完全与数据大小无关,因为文件大小与NiFi无关。
Kafka就像一个将数据存储在Kafka主题中的邮箱,等待应用程序发布和/或使用它。NiFi就像邮递员一样,将数据传递到邮箱或其他目的地。
NiFi提供了广泛的协议(MQTT、Kafka协议、HTTP、Syslog、JDBC、TCP / UDP等)可以在数据导入时进行交互。NiFi是一款出色、一致且独特的软件,可以管理您的所有数据提取。您可能要考虑将数据发送到Kafka,以用于多个下游应用程序。但是,NiFi应该成为获取数据的网关,因为它支持多种协议,并且可以在相同的简单拖放界面中满足数据需求,从而使ROI很高。
使用NiFi将数据安全地移动到多个位置,尤其是采用多云策略时。
Kafka Connect可以回答一些问题,但是当您在移动数据时需要复杂的过滤、路由、扩充和转换时,这不是通用的解决方案。
NiFi还基于可扩展框架构建,该框架为用户提供了简便的方法来扩展NiFi的功能并快速构建非常自定义的数据移动流。
大规模公开用于实时数据收集的REST API的最佳方法是什么?
我们的客户使用NiFi公开REST API,供外部来源将数据发送到目的地。最常见的协议是HTTP。

如果您的目标是获取数据,则可以在NIFi中使用ListenHTTP处理器,让它侦听HTTP请求的给定端口,然后可以向其发送任何数据。
如果要使用NiFi提供Web服务,请查看HandleHTTPRequest和HandleHTTPResponse处理器。通过使用两个处理器的组合,您将通过HTTP接收来自外部客户端的请求。您将能够对请求中的数据进行处理,并将自定义答案/结果发送回客户端。例如,您可以使用NiFi通过HTTP访问外部系统,例如FTP服务器。您将使用两个处理器并通过HTTP发出请求。当您在NIFi中收到查询时,NiFi会针对FTP服务器进行查询以获取文件,然后将文件发送回客户端。
使用NiFi,所有这些独特的请求都可以很好地扩展。在这种用例中,NiFi将根据需求进行水平扩展,并在NiFi实例的前面设置负载均衡器,以平衡集群中NiFi节点之间的负载。
是否可以根据用户的访问权限和安全策略阻止或共享NiFi数据流?
NiFi提供了非常细粒度的多租户和策略模型。设置正确的策略以在多租户环境中提供NiFi很容易。您可以轻松地在NiFi中使用不同的策略集定义多个流程组,因此您有一个专用于处理用例1的团队A的流程组,以及一个专用于用例2的团队B的流程组。考虑:

NiFi确保不同的团队不应该访问其他流程组。使用Apache Ranger或NiFi中的内部策略可以轻松进行设置。您可以让多个团队在同一个NiFi环境中处理大量用例。
在NiFi集群中,所有资源均由所有现有流共享,并且没有资源隔离。例如,NiFi无法为用例#1分配60%的资源,而为用例#2分配40%的资源。对于关键用例,大多数客户将拥有专用的NiFi群集,以确保满足SLA。NiFi提供了监视功能,以确保在群集内正确使用资源并在群集过小时发出警报。
在2021年,Cloudera将发布新解决方案,使客户能够在大小合适的专用NiFi群集中运行NiFi流,并在自动缩放(上下)的k8上运行。此选项可确保每个用例在一段时间内使用所需的内容,而不会影响其他用例。
NiFi是否可以很好地替代ETL和批处理?
对于某些用例,NiFi当然可以代替ETL,也可以用于批处理。但是,应该考虑用例所需的处理/转换类型。在NiFi中,流文件是描述流过事件、对象和数据的方式。虽然您可以在NiFi中为每个Flow File执行任何转换,但您可能不想使用NiFi将Flow File基于公共列连接在一起或执行某些类型的窗口聚合。在这种情况下,Cloudera建议使用其他解决方案。

那么有什么建议呢?

在流使用情况下,最好的选择是使用NiFi中的记录处理器将记录发送到一个或多个Kafka主题。然后,基于我们对Eventador的收购,您可以让Flink使用Continuous SQL对数据进行所有想要的处理(加入流或执行窗口操作)。
在批处理用例中,您会将NiFi视为ELT而不是ETL(E =提取,T =转换,L =加载)。NiFi会捕获各种数据集,对每个数据集进行所需的转换(模式验证、格式转换、数据清理等),然后将数据集发送到由Hive支持的数据仓库中。将数据发送到那里后,NiFi可能会触发Hive查询以执行联合操作。

File to HDFS

在这里插入图片描述

FIle to Kafka

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

hive_to_mysql

在这里插入图片描述

kafka2Hdfs

在这里插入图片描述

复杂案例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

MiNiFi 和 NiFi 有什么区别?

MiNiFi 是用于从远程位置的传感器和设备上收集数据子集的代理。目的是帮助进行数据的“第一英里收集”,并获取尽可能接近其来源的数据。

这些设备可以是服务器、工作站和便携式计算机,也可以是传感器、自动驾驶汽车、工厂中的机器等,您希望在其中使用 MiNiFi 中的某些 NiFi 功能来收集特定数据。在将数据发送到目的地之前,可以对其进行过滤、选择和分类。MiNiFi 的目标是使用 Edge Flow Manager 大规模管理整个流程,以便运营或 IT 团队可以部署不同的流程定义并根据业务需要收集任何数据。以下是一些需要考虑的细节:

NiFi 被设计为通常位于数据中心或云中的中央位置,以在已知的外部系统(如数据库、对象存储等)中移动数据或从中收集数据。NiFi 应该被视为将数据移回的网关在异构环境或混合云体系结构中来回切换。
MiNiFi 在主机上本地运行,进行一些计算和逻辑运算,并且仅将您关心的数据发送到外部系统以进行数据分发。这样的系统当然可以是 NiFi,但也可以是 MQTT 代理、云提供商服务等。MiNiFi 还支持一些用例,在这些用例中,网络带宽可能受到限制,需要减少通过网络发送的数据量。
MiNiFi 代理有两个版本:C ++ 和 Java。MiNiFi C ++ 选项的占用空间非常小(几 MB 的内存,很少的 CPU),但是可用的处理器却更少。MiNiFi Java 选项是轻量级的 NiFi 单节点实例,是 NiFi 的无头版本,他没有用户界面也没有集群功能。尽管如此,它仍要求 Java 在主机上可用。

如果可以使用 Kafka 作为群集的入口点

为什么还要使用 NiFi ?

这是一个很好的问题,许多参加我的 Live NiFi Demo Jam 的人都问了这个问题。您可以通过以下方式确定何时使用 NiFi 和何时使用 Kafka。

Kafka 设计用于主要针对较小文件的面向流的用例,然而摄取大文件不是一个好主意。NiFi 完全与数据大小无关,因为文件大小与 NiFi 无关。
Kafka 就像一个将数据存储在 Kafka 主题中的邮箱,等待应用程序发布和/或使用它。NiFi 就像邮递员一样,将数据传递到邮箱或其他目的地。
NiFi 提供了广泛的协议(MQTT、Kafka 协议、HTTP、Syslog、JDBC、TCP / UDP 等)可以在数据导入时进行交互。NiFi 是一款出色、一致且独特的软件,可以管理您的所有数据提取。您可能要考虑将数据发送到 Kafka,以用于多个下游应用程序。但是,NiFi 应该成为获取数据的网关,因为它支持多种协议,并且可以在相同的简单拖放界面中满足数据需求,从而使 ROI 很高。
使用 NiFi 将数据安全地移动到多个位置,尤其是采用多云策略时。
Kafka Connect 可以回答一些问题,但是当您在移动数据时需要复杂的过滤、路由、扩充和转换时,这不是通用的解决方案。
NiFi 还基于可扩展框架构建,该框架为用户提供了简便的方法来扩展 NiFi 的功能并快速构建非常自定义的数据移动流。

大规模公开用于实时数据收集的 REST API 的最佳方法是什么?

我们的客户使用 NiFi 公开 REST API,供外部来源将数据发送到目的地。最常见的协议是 HTTP。
如果您的目标是获取数据,则可以在 NIFi 中使用 ListenHTTP 处理器,让它侦听 HTTP 请求的给定端口,然后可以向其发送任何数据。

如果要使用NiFi提供Web服务,请查看HandleHTTPRequest 和HandleHTTPResponse 处理器。通过使用两个处理器的组合,您将通过 HTTP 接收来自外部客户端的请求。您将能够对请求中的数据进行处理,并将自定义答案/结果发送回客户端。例如,您可以使用 NiFi 通过 HTTP 访问外部系统,例如 FTP 服务器。您将使用两个处理器并通过 HTTP 发出请求。当您在 NIFi 中收到查询时, NiFi 会针对 FTP 服务器进行查询以获取文件,然后将文件发送回客户端。

使用 NiFi,所有这些独特的请求都可以很好地扩展。在这种用例中,NiFi 将根据需求进行水平扩展,并在 NiFi 实例的前面设置负载均衡器,以平衡集群中 NiFi 节点之间的负载。

是否可以根据用户的访问权限和安全策略阻止或共享 NiFi 数据流?

NiFi 提供了非常细粒度的多租户和策略模型。设置正确的策略以在多租户环境中提供 NiFi 很容易。您可以轻松地在 NiFi 中使用不同的策略集定义多个流程组,因此您有一个专用于处理用例1的团队 A 的流程组,以及一个专用于用例2的团队B的流程组。考虑:
NiFi 确保不同的团队不应该访问其他流程组。使用 Apache Ranger 或 NiFi 中的内部策略可以轻松进行设置。您可以让多个团队在同一个 NiFi 环境中处理大量用例。
在 NiFi 集群中,所有资源均由所有现有流共享,并且没有资源隔离。例如,NiFi 无法为用例#1分配60%的资源,而为用例#2分配40%的资源。对于关键用例,大多数客户将拥有专用的 NiFi 群集,以确保满足 SLA。NiFi 提供了监视功能,以确保在群集内正确使用资源并在群集过小时发出警报。
在2021年,Cloudera 将发布新解决方案,使客户能够在大小合适的专用 NiFi 群集中运行 NiFi 流,并在自动缩放(上下)的k8上运行。此选项可确保每个用例在一段时间内使用所需的内容,而不会影响其他用例。

NiFi 是否可以很好地替代 ETL 和批处理?

对于某些用例,NiFi 当然可以代替 ETL,也可以用于批处理。但是,应该考虑用例所需的处理/转换类型。在 NiFi 中,流文件是描述流过事件、对象和数据的方式。虽然您可以在 NiFi 中为每个 Flow File 执行任何转换,但您可能不想使用 NiFi 将 Flow File 基于公共列连接在一起或执行某些类型的窗口聚合。在这种情况下,Cloudera 建议使用其他解决方案。

那么有什么建议呢?

在流使用情况下,最好的选择是使用 NiFi 中的记录处理器将记录发送到一个或多个 Kafka 主题。然后,基于我们对 Eventador 的收购,您可以让 Flink 使用 Continuous SQL 对数据进行所有想要的处理(加入流或执行窗口操作)。
在批处理用例中,您会将 NiFi 视为 ELT 而不是 ETL(E =提取,T =转换,L =加载)。NiFi 会捕获各种数据集,对每个数据集进行所需的转换(模式验证、格式转换、数据清理等),然后将数据集发送到由 Hive 支持的数据仓库中。将数据发送到那里后,NiFi 可能会触发 Hive 查询以执行联合操作。

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值