本文仅对我理解的Nifi做简单介绍,个人能力有限,若有不当,敬请谅解!
通过本文旨在能帮您实现Nifi的入门使用
简介
官方概述比较高大上,我反正认为简单来说就是一个可靠、好用的ETL工具,以下从我个人角度来阐述我认为的Nifi特点:
- BS 架构,除了实现在浏览器进行流水线配置外,更重要的是避免了Kettle以前必须要求宿主机能够使用桌面(Kettle 社区版有Web界面支持),这点做过政务的同学应该比较清楚,政务网的机器大多不会开放过多权限,BS这种方式仅仅需要开放端口。
- 扩展友好,这点并不是说Kettle的扩展性差,而是体现在自定义开发组件时候,Nifi的门槛低,使用其提供的基础SDK,熟悉Java和Nifi使用的人可以很容易上手开发自己特定业务的组件。
- 界面清爽,颜值即正义。
- 调试追踪数据简单方便。
- 自带组件已能满足绝大多数需求,自带的处理器包含能够接入:文件、Http、Tcp、数据库、Mq等数据源的数据采集,以及Json2Json的指定转换和多数据源推送。
- 可靠性强,因为其采用了Write-Ahead Log(熟悉MySQL的同学应该很清楚redo.log)机制,能够保证在极端情况下数据能够恢复(当然不是绝对的,不要当成灵丹妙药来拯救病危的系统,最好需要有自己的审计机制(比如批量同步前后可以先有个包含数量任务生成,便于前后对比,这点也可以借助Nifi实现),当然绝大部分情况下是不会发生丢数据,但是免费的东西怎么能嫌弃他不够完美呢)。而且在压力大的时候,可以触发背压机制,自动条件流水线的组件执行速率。
- 调度设置简单,作为一个同步工具是一个刚需需求,Nifi支持多种调度方式。
官方资料
https://nifi.apache.org/documentation/v2/ 哈姆雷特,遇到棘手问题还是问提供解决方案的人吧!
安装
支持以软件包和容器方式安装,学习阶段可以采用容器方式快速启动,如果存在一些定制化组件二开需求,可以采用离线文件部署,或者在容器启动方式下将相应目录映射到宿主机磁盘上面。集群部署,本人暂未实际使用过该模式,故不阐述相关,但用法单机和集群并无本质上区别。
详细操作参考官方文档:https://nifi.apache.org/documentation/nifi-2.0.0-M4/html/getting-started.html
😇
整体架构
Nifi整体架构如图:
从图可知:
他有面向Web端的模块,有多个处理器(Processor)和 扩展模块(Extension),和三个仓库。
其中:
- Flowfile Repository
充当预写式日志的实现,每个修改Flowfile(一个关键属性和内容的逻辑概念体,流经每个处理器的都是一个一个的Flowfile) 操作都会先记录在Flowfile Repository中,同时其有固定的检查点,可以完成定期归档。在发生崩溃的时候,使用Flowfile Repository重放来完成Flowfile的恢复。 - Content Repository
存储当前和过去(未被清理)的Flowfile的内容部分。 - Provenance Repository
用于存储Flowfile的追踪溯源
核心概念
Nifi整体概念较多,此处只对部分最具代表性或者常用的概念做介绍。
Flowfile
FlowFile是一个数据记录,它由指向其内容(有效负载)的指针和支持内容的属性组成,与一个或多个起源事件相关联(官方文档译文)。
也就是说其并不是一个真实的文件,而只是一个逻辑上分为两部分:属性和内容的一个数据,这点我觉得类比Http的报文方式能够更好理解,属性就是类比Http的Header里面的内容,主要用于对内容数据做描述如类型,或用于表示某个控制信息如状态码之类的,而内容对应Http的内容段代表真实的数据信息。分成两个部分除了考虑其内部性能的情况外,对用户来说,提供了更便捷的操作性,可以根据具体的业务场景逻辑修改属性信息而不修改数据达到控制的效果。
这个是一个必须要理解的概念,因为要实现你的需求,方法灵活多变,但是你直接操作的数据属性和内容必须要有一个预期和切入点,而 Flowfile 可以简单认为是其中流转的每条数据,借助属性和内容两部分可以单独操作的特性从而完成业务需求。
Processor
处理器,是指各种转换和输入输出的组件,其可以通过界面的方式进行相关配置从而完整指定的业务功能。
主要分为:输入、输出、转换三大类。
可以根据类型或条件快速检索需要的处理器。
ProcessSession
进程会话,这个主要是做处理器开发的时候会用到,其用于控制一个Flowfile的创建、修改。
@Override
public void onTrigger(final ProcessContext context, final ProcessSession session) {
FlowFile flowFile = session.get();
if (flowFile == null) {
return;
}
session.transfer(flowFile, ORIGIN_RELATIONSHIP);
}
上诉时简单示意其可以操作Flowfile。
Controller Service
因为处理器只是一个简单的数据处理环境,其是完全无状态的,但是有时候需要一个有状态的组件或者说一个可被很多处理器共用的组件。如计算某类型数据的时候,借助外部某个api,这个api可以缓存外部api或者预先加载外部数据,从而帮助处理器快速计算这种组件称为Controller Service(有点抽象)。
下面以一个具体的处理器 ExecuteSQL为例
从其设置界面可以看到,其必填项(黑体加粗的)第一个是需要一个数据库连接池服务,这也很符合我们Java开发逻辑,我们的ORM框架需要一个连接池。而数据库连接池可以被多个需要数据库连接的处理器复用,比如我可以从一个库的A表抽数加工计算到B表。
从源码的角度来讲,处理器声明对某个服务的依赖为接口形式,这也符合依赖倒置原则,因为一种服务可以允许拥有多个具体服务落地实现。
Relationships
这个标注了每个组件下面连线的可选类型,即输出的分支。每一个组件的所有关系必须全部有方向,除了选择传递到后续处理器,也可以选择终止和重试其处理器自身回环该分支,否则该处理器状态会处于Disable状态,即无法启动。传递到后面处理器的方式为选择该处理器,出现箭头后拖动至后续处理器,同时该连线需要选择Relationships,该连线也会对应一个队列,采用先进先出的方式将上游FLowfile的引用传递给下游处理器。
表达式
https://nifi.apache.org/documentation/nifi-2.0.0-M4/html/expression-language-guide.html
Nifi很多操作,比如函数调用:字符串截取、时间格式化、替换字符串,使用过Spring SpEL的可以很快上手,官方的Demo用法基本涵盖了,绝大部分用法,但是在嵌套多层表达式的时候需要自己测试。
Demo
话不多说,直接上Demo
通过RestApi 翻页爬取数据
1. 环境准备
我是本地使用虚拟机在linux之中安装了Nifi,宿主机上面跑一个简单的Spring程序,因为使用Nat网络,因此可以形成本地局域网。Springboot简易代码如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SimpleUser {
private Long id;
private String name;
private int age;
}
@RestController
@RequestMapping("/page")
@Slf4j
public class PageTest {
private final List<SimpleUser> users;
public PageTest() {
users = new ArrayList<>();
for (int i = 0; i < 100; i++) {
users.add(new SimpleUser((long) i, "name" + i, i));
}
}
@GetMapping("/getPage")
public List<SimpleUser> getUsers(int page, int size) {
int startIndex = (page - 1) * size;
if (startIndex >= users.size()) {
return new ArrayList<>();
}
int endIndex = Math.min(startIndex + size, users.size());
return users.subList(startIndex, endIndex);
}
}
2. 初始测试如图
此次仅仅验证调用是否可通,后面会讲从头设计思路
解释:
- GenerateFlowFile 会创建一个Flowfile,当你没有什么组件作为第一个输入源,或者说你需要预先准备一些条件之类的时候,使用这个是个不错的选择,我这目前仅使用其作为整条流水线的启动入口。因为默认的处理器调度是有待处理数据即执行,即后面的是以类事件驱动的方式等待运行。
- UpdateAttribute 用来修改FLowfile的属性段,一般我们可以用来做条件之类的数据补充信息。
- InvokeHTTP 发起一个Http 调用,没啥特别说法。
- LogAttribute 日志打印,当然因为其不涉及到数据转换操作,常常可以将其加入流水线但是维持Stop状态,从而让其对应的入口队列阻塞便于调试数据。
- Funnel 可以进行多入口的整合,让你连线看起来没那么乱,当然这个多的连线是否会多产生队列?请瞅瞅官网。
其中InvokeHTTP设置如图,其他均采用默认设置,每次设置完,可以点击小√进行检查是否合法。
3. 调试
选择每个处理器,右键修改状态,将第一个和最后一个置为非启动状态。
然后选择第一个组件右键选择执行一次(默认调度是一直执行,一般第一个即输入数据的处理器一直启动要配合定时调度)
其中Http组件设置的URL为 http://192.168.231.1:8080/springboot-mvc/page/getPage?page=1&size=10
最后的日志组件前面Response 队列存在一个待消费数据,右键选择
List Queue,即可查看队列数据
查看队列发现,队列中有一条待处理数据,其中有以下字段:
- Position 表示这条数据位于的队列位置
- UUID Flowfile唯一ID,属性字段
- Filename 文件名,如果采用PutFile处理器将Flowfile输出到磁盘,会将该属性作为文件名,可以通过UpdateAttribute处理器修改
- File Size Flowfile大小
- 后面三个字段标准入栈时间以及是否过期,一般不是特别敏感。
- 后面三个可操作的分别表示,下载、查看、查看源(即数据整体处理的追踪)
点击查看按钮,即可查看数据:
说明整体调用是OK的
4. 细化
1. 流程设计
我们前面实现了一次调用固定的页和页宽,接下来我们实现循环翻页,直到数据返回为空。
从上而知,我们需要根据一个条件:返回数据不为空,即证明可能还有下一页,我们就需要往下翻页,因此这里有一个条件判断和一个流程回环,简易流程图如下:
上述需要注意的是页面信息存储在Flowfile属性中而结果存储在内容段。
2. 流程实现
1. 添加翻页信息到属性
我们之间在 UpdateAttribute 处理器上面添加两个字段,page_size,paze_num。如下图:
2. InvokeHttp组件设置读取属性作为条件
借助Nifi表达式实现。
3. 调用测试
查看队列发现我们的调用成功,接下来就是解决怎么判断是否非空和更新翻页信息。
4. 判断非空和更新页码-路由
因为我们采用的JSON方式序列化的,那么我们可以直接使用其针对JSON方面的支持处理器。
我这里使用 支持JSONPath方式的处理器EvaluateJsonPath 提取返回数组的长度。
使用 RouteOnAttribute 进行路由,路由条件即data_size是否大于0
整体回环如图
如果不为空则通过 UpdateAttribute 中 page_num设置为 ${page_num:plus(1)}
表达式并将下一个处理器指向 InvokeHttp即可完成翻页调用。
其中:
Nifi中路由的选择可以基于属性和内容,而下游的RelationShip也可以不仅是match和UnMatch两个,可以根据实际的需求实现多出路,我这里演示的仅有两个分支:有数据和无数据。
5. 运行
将各组件分别设置状图如图,即 GenerateFlowFile 和两个Log其他的均保持为运行时状态。
选择 GenerateFlowFile 右键运行一次。
结果如图,与预期相符,我们总共有100个数据,每页10个,需要调用11次,前10次是有数据的,最后一次为空即退出调用,注意的是,上述队列的数据不是绝对即时的,可以通过刷新页面或者等待一下出现,Nifi统计相关也是需要消耗一定的资源。
后语
本来还想写个Http Post即数据库抽取的Demo,但是时间有限,而且做一个Demo 基本上都能够理解一整套逻辑,就不在过多赘述了。
只要明白每一个FLowfile分为属性和内容两部分这个最核心的特性,结合一点灵活使用内置组件即可解决绝大多数ETL场景。