【Apache NIFI 翻译】5-Apache NiFi RecordPath Guide

【Apache NIFI 翻译】5-Apache NiFi RecordPath Guide

原文地址:http://nifi.apache.org/docs.html
参考博客:https://nifichina.github.io/1-%E5%9F%BA%E7%A1%80%E6%96%87%E6%A1%A3/5-RecordPathGuide.html

Overview

    Apache NiFi提供了一组非常强大的处理器,能够提取,处理,路由,转换和传递任何格式的数据。这是因为NiFi框架本身与数据无关。不管您的数据是100字节JSON消息还是100 GB的视频。这是一个非常强大的功能。除此之外还有有许多模式已迅速发展为处理不同类型的数据。

    NiFi经常处理的一类数据是面向record的数据。当我们说面向record的数据时,我们经常(但并非总是)谈论诸如JSON,CSV和Avro之类的结构化数据。但是,还有许多其他类型的数据也可以表示为“records”或“messages”。NIFI提供了一组Controller Services,用于解析这些不同的数据格式并使用RecordReader API以一致的方式表示数据。只要有一个RecordReader能够产生代表该数据的Record对象,就可以将以任何数据格式写入的数据视为相同。

    当我们谈论record时,这是一种抽象,它使我们能够以相同的方式处理数据,而不管其采用的格式。record由一个或多个字段组成。每个字段都有一个名称和与之关联的类型。使用record schema描述record的字段。schema指示哪些字段构成特定类型的record。字段的类型将是以下之一:

  • String
  • Boolean
  • Byte
  • Character
  • Short
  • Integer
  • Long
  • BigInt
  • Float
  • Double
  • Date - 表示没有Time部分的日期
  • Time - 表示不包含Date部分的一天中的时间
  • Timestamp - Represents a Date and Time
  • Embedded Record - 可以通过允许字段本身为Type Record来表示诸如JSON之类的分层数据。
  • Choice - 字段可以是几种类型中的任何一种。
  • Array - 数组的所有元素都具有相同的类型。
  • Map - 所有映射键的类型均为字符串。值是同一类型。

    将数据流转换为Records后,RecordWriter API允许我们将这些Records序列化回字节流,以便可以将它们传递到其他系统上。

    当然,如果我们不打算对两者之间的数据做任何事情,那么读取和写入这些数据就没有多大意义。NiFi提供了多种处理器,它们提供了一些非常强大的功能,用于路由,查询和转换面向record的数据。通常,为了执行所需的功能,处理器将需要来自用户的输入,以便确定应对record中的哪些字段或对record中的哪些值进行操作。

    进入NiFi RecordPath语言。 RecordPath旨在成为一种简单易用的领域特定语言(DSL),用于指定在配置处理器时我们关心或希望访问的record中的哪些字段。

Structure of a RecordPath

    NiFi中的record由(可能)许多字段组成,并且这些字段中的每个字段实际上都可以是record。这意味着可以将record视为具有层次结构或嵌套结构。我们将“inner Record”称为“outer Record”的子代。因此,内部Record的子级是最外部record的后代。同样,我们可以将外部record称为内部record的祖先。

Child Operator

    RecordPath语言的结构使我们能够轻松引用最外层Record的字段,子Record或后代Record的字段。为此,我们用斜杠字符(/)分隔子代的名称,我们将其称为子运算符。例如,假设我们有一个包含两个字段的record:name,details。此外,假设详细信息是一个字段,该字段本身就是一个Record,并且具有两个字段:identifier,address。此外,让我们考虑一下地址本身就是一个包含5个字段的record:number, street, city, state, zip。为了说明目的,此处以JSON编写的示例可能如下所示:

{
	"name": "John Doe",
	"details": {
		"identifier": 100,
		"address": {
			"number": "123",
			"street": "5th Avenue",
			"city": "New York",
			"state": "NY",
			"zip": "10020"
		}
	}
}

    我们可以使用RecordPath来引用zip字段:/details/address/zip。这告诉我们我们要使用“root”record的详细信息字段。然后,我们要引用子record的address字段和该record的zip字段。

Descendant Operator

    除了提供到达zip字段的显式路径之外,有时在不知道完整路径的情况下引用zip字段有时可能很有用。在这种情况下,我们可以使用后代运算符(//)代替子运算符(/)。要获得与上述相同的zip字段,我们只需使用路径//zip即可完成此操作。

    但是,子运算符和后代运算符之间有一个非常重要的区别:后代运算符可以匹配许多字段,而子运算符最多可以匹配一个字段。为了帮助理解这一点,请考虑以下record:

{
	"name": "John Doe",
	"workAddress": {
		"number": "123",
		"street": "5th Avenue",
		"city": "New York",
		"state": "NY",
		"zip": "10020"
	},
	"homeAddress": {
		"number": "456",
		"street": "116th Avenue",
		"city": "New York",
		"state": "NY",
		"zip": "11697"
	}
}

    现在,如果我们使用RecordPath /workAddress/zip,我们将引用值为“10020”的zip字段。 RecordPath /homeAddress/zip将引用值为“11697”的zip字段。但是,RecordPath //zip将引用这两个字段。

Filters

    通过以上示例和说明,我们可以轻松引用Record中的特定字段。但是,在实际情况下,数据很少像上面的示例那样简单。通常,我们需要过滤掉或优化我们要引用的字段。当我们引用一个Array字段并且只想引用该数组中的某些元素时,可能需要这样做。当我们引用Map字段并希望引用Map中的一个或几个特定条目时;或者仅在符合某些条件的情况下我们才想引用record。我们可以通过在方括号内为RecordPath提供条件(使用[,]字符)来完成此操作。我们将在下面介绍每种情况。

Function Usage

    如上文“Filters”部分所述,除了从record中检索字段外,有时我们还需要优化我们要选择的字段。或者,我们可能想返回字段的修改后版本。为此,我们依靠功能。函数的语法为<function name> <open parenthesis> <args> <close parenthesis>,其中<args>表示一个或多个用逗号分隔的参数。参数可以是字符串文字(如“hello”)或数字文字(如48),也可以是相对或绝对的RecordPath(如./name或/id)。另外,我们可以在过滤器中使用函数。例如,我们可以使用诸如/person[isEmpty('name')]/id之类的RecordPath来检索名称为空的任何人的id字段。可用功能列表及其相应文档可在下面的“Functions”部分中找到。

Arrays

    当我们引用数组字段时,该字段的值可能是一个包含多个元素的数组,但我们可能只需要其中的几个元素。例如,我们可能只想引用第一个元素;只引用最后一个元素;或者可能引用第一个、第二个、第三个和最后一个元素。我们可以简单地使用方括号内元素的索引来引用特定元素(索引是从0开始的)。因此,让我们考虑一下上述record的修改版本:

{
	"name": "John Doe",
	"addresses": [
		"work": {
			"number": "123",
			"street": "5th Avenue",
			"city": "New York",
			"state": "NY",
			"zip": "10020"
		},
		"home": {
			"number": "456",
			"street": "116th Avenue",
			"city": "New York",
			"state": "NY",
			"zip": "11697"
		}
	]
}

    我们现在可以使用RecordPath /addresses[0]引用addresses数组中的第一个元素。我们可以使用RecordPath /addresses[1]访问第二个元素。不过,有时我们不知道数组中会有多少个元素。所以我们可以使用负索引从数组的末尾倒数。例如,我们可以将最后一个元素作为/addresses[-1]访问,也可以将倒数第二个元素作为/addresses[-2]访问。如果要引用多个元素,可以使用逗号分隔的元素列表,例如/addresses[0,1,2,3]。或者,要访问元素0到8,我们可以使用范围运算符(..),如/addresses[0..8]。我们还可以混合使用这些元素,并使用语法/addresses[0..-1]或甚至/addresses[0,1,4,6..-1]引用所有元素。当然,并不是这里引用的所有索引都与上面的record匹配,因为addresses数组只有2个元素。不匹配的索引将被跳过。

Maps

    与数组字段类似,映射字段实际上可能由几个不同的值组成。RecordPath使我们能够根据键选择一组值。我们通过在方括号内使用带引号的字符串来实现这一点。作为一个例子,让我们从上面重新访问我们的原始record:

{
	"name": "John Doe",
	"details": {
		"identifier": 100,
		"address": {
			"number": "123",
			"street": "5th Avenue",
			"city": "New York",
			"state": "NY",
			"zip": "10020"
		}
	}
}

    现在,让我们假设与这个Record相关联的Schema指示address字段不是Record而是Map。在这种情况下,如果我们试图使用RecordPath/details/address/zip引用zip,那么RecordPath将不匹配,因为address字段不是record,因此没有任何名为zip的子record。相反,它是一个Map,具有String类型的键和值。不幸的是,当查看JSON时,这可能看起来有点混乱,因为JSON没有真正的类型系统。但是,当我们将JSON转换为Record对象以便对数据进行操作时,这种区别可能很重要。

    在上述情况下,我们仍然可以使用RecordPath访问zip字段。现在我们必须使用稍微不同的语法:/details/address['zip']。这告诉RecordPath我们要访问最高级别的details字段。然后我们要访问它的address字段。因为address字段是一个Map,所以我们可以使用方括号来表示要指定一个映射键,然后用引号指定键。

    此外,我们可以使用逗号分隔的列表选择多个映射键:/details/address['city','state','zip']。如果需要,我们还可以使用通配符(*):/details/address[*]选择所有字段。映射字段不包含任何排序,因此无法通过数字索引引用键。

Predicates

     到目前为止,我们已经讨论了两种不同类型的过滤器。每种方法都允许我们从允许多个值的字段中选择一个或多个元素。但是,通常我们需要应用一个过滤器,允许我们限制选择哪些record字段。例如,如果我们想选择zip字段,但只为值不是纽约的address字段选择呢?上面的例子并没有给我们提供任何方法来做到这一点。

    RecordPath为用户提供了指定谓词的能力。谓词只是一个过滤器,它可以应用于一个字段,以确定该字段是否应包含在结果中。与其他过滤器一样,谓词在方括号内指定。谓词的语法是<Relative RecordPath><Operator><Expression>。Relative RecordPath的工作方式与其他RecordPath相同,但必须以.(参考当前字段)或..(引用当前字段的父字段)开头而不是斜杠,并引用与谓词应用的字段相关的字段。操作必须是以下之一:

  • Equals (=)
  • Not Equal (!=)
  • Greater Than (>)
  • Greater Than or Equal To (>=)
  • Less Than (<)
  • Less Than or Equal To (<=)

    表达式可以是文字值,例如50或Hello,也可以是另一个RecordPath。为了说明这一点,让我们以下面的record为例:

{
	"name": "John Doe",
	"workAddress": {
		"number": "123",
		"street": "5th Avenue",
		"city": "New York",
		"state": "NY",
		"zip": "10020"
	},
	"homeAddress": {
		"number": "456",
		"street": "Grand St",
		"city": "Jersey City",
		"state": "NJ",
		"zip": "07304"
	},
	"details": {
		"position": "Dataflow Engineer",
		"preferredState": "NY"
	}
}

    现在我们可以使用谓词来仅选择state不是New York的字段。 例如,我们可以使用/*[./state!='NY']. 如果state不具有“NY”值,则它将选择任何具有state字段的record字段。请注意,将不返回详细details Record,因为它没有名为state的字段。因此,在此示例中,RecordPath将仅选择homeAddress字段。一旦选择了该字段,就可以继续我们的RecordPath。如上所述,我们可以选择zip字段:/*[./state!='NY']/zip。此RecordPath将导致仅从homeAddress字段中选择zip字段。

我们还可以将一个字段中的值与另一字段中的值进行比较。例如,我们可以使用RecordPath /*[./state = /details/preferredState]选择处于该人的首选状态的地址。在此示例中,此RecordPath将检索workAddress字段,因为其状态字段与preferredState字段的值匹配。

另外,我们可以通过使用父运算符(..):/*/city[../state ='NJ']来写一个RecordPath来引用状态为“NJ”的任何record的“city”字段。

Functions

    在上面的“Function Usage”部分,我们描述了如何以及为什么在RecordPath中使用函数。在这里,我们将描述可用的不同功能,它们的功能以及它们的工作方式。函数可以分为两类:Standalone Functions(可以是RecordPath的“root”,例如substringAfter(/name,''))和Filter Functions(它们可以用作过滤器,例如/name[ contains('John')]。Standalone Functions也可以在过滤器中使用,但不返回布尔值(true或false),因此本身不能是整个过滤器。例如,我们可以使用诸如/name[ substringAfter(., ' ') = 'Doe']之类的路径,但是我们不能简单地使用/name[ substringAfter(., ' ') ],因为这样做实际上没有任何意义,因为过滤器必须为布尔值。

    除非另有说明,否则下面的所有示例均将根据以下record进行操作:

{
	"name": "John Doe",
	"workAddress": {
		"number": "123",
		"street": "5th Avenue",
		"city": "New York",
		"state": "NY",
		"zip": "10020"
	},
	"homeAddress": {
		"number": "456",
		"street": "Grand St",
		"city": "Jersey City",
		"state": "NJ",
		"zip": "07304"
	},
	"details": {
		"position": "Dataflow Engineer",
		"preferredState": "NY",
		"employer": "",
		"vehicle": null,
		"phrase": "   "
	}
}

Standalone Functions

Filter Functions

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值