用户自定义函数(UDF)

介绍

Flink的DataStream API编程风格其实是一致的:基本上都是基于DataStream调用一个方
法,表示要做一个转换操作;方法需要传入一个参数,这个参数都是需要实现一个接口。
这些接口有一个共同特点:全部都以算子操作名称 + Function命名,例如源算子需要实
现SourceFunction接口,map算子需要实现MapFunction接口,reduce()算子需要实现
ReduceFunction接口。我们不仅可以通过自定义函数类或者匿名类来实现接口,也可以直接传
入Lambda表达式。这就是所谓的用户自定义函数(user-defined function,UDF)。
接下来我们就对这几种编程方式做一个梳理总结。

1. 函数类(Function Classes)

对于大部分操作而言,都需要传入一个用户自定义函数(UDF),实现相关操作的接口,
来完成处理逻辑的定义。Flink暴露了所有UDF函数的接口,具体实现方式为接口或者抽象类,
例如MapFunction、FilterFunction、ReduceFunction等。
所以最简单直接的方式,就是自定义一个函数类,实现对应的接口。
下面例子实现了FilterFunction接口,用来筛选url中包含“home”的内容

import org.apache.flink.api.common.functions.FilterFunction 
import org.apache.flink.streaming.api.scala._ 
 
object TransFunctionUDFTest { 
  def main(args: Array[String]): Unit = { 
    val env = StreamExecutionEnvironment.getExecutionEnvironment 
    env.setParallelism(1) 
 
    val clicks = env 
      .fromElements( 
        Event("Mary", "./home", 1000L), 
        Event("Bob", "./cart", 2000L) 
      ) 
 //通过传入自定义FilterFunction实现过滤 
    val stream1 = clicks.filter(new FlinkFilter) 
 
    stream1.print() 
 
env.execute() 
} 
//自定义FilterFunction函数类 
class FlinkFilter extends FilterFunction[Event] { 
override def filter(value: Event): Boolean = value.url.contains("home") 
} 
} 

当然还可以通过匿名类来实现FilterFunction接口:

val filterdStream = stream.filter(new FilterFunction[Event] { 
override def filter(value: Event): Boolean = value.url.contains("home") 
}) 

为了类可以更加通用,我们还可以将用于过滤的关键字“home”抽象出来作为类的属性,
调用构造方法时传进去。

stream.filter(new KeywordFilter("home")).print() 
//自定义FilterFunction函数类,将需要用到的过滤参数作为类的构造参数传入 
class KeywordFilter(keyword: String) extends FilterFunction[Event] { 
override def filter(value: Event): Boolean = value.url.contains(keyword) 
} 

对于Scala这样的函数式编程语言,更为简单的写法是直接传入一个Lambda表达式: stream.filter(_.url.contains("home")).print()

这样我们用一行代码就可以搞定,显得更加简洁明晰。

2. 富函数类(Rich Function Classes)

“富函数类”也是DataStream API提供的一个函数类的接口,所有的Flink函数类都有其
Rich 版本。富函数类一般是以抽象类的形式出现的。例如:RichMapFunction、RichFilterFunction、
RichReduceFunction 等。
与常规函数类的不同主要在于,富函数类可以获取运行环境的上下文,并拥有一些生命周
期方法,所以可以实现更复杂的功能。
典型的生命周期方法有:
⚫ open()方法,是Rich Function 的初始化方法,也就是会开启一个算子的生命周期。当
一个算子的实际工作方法例如 map()或者 filter()方法被调用之前,open()会首先被调
用。所以像文件IO流的创建,数据库连接的创建,配置文件的读取等等这样一次性
的工作,都适合在open()方法中完成。
⚫ close()方法,是生命周期中的最后一个调用的方法,类似于解构方法。一般用来做一
些清理工作。
需要注意的是,这里的生命周期方法,对于一个并行子任务来说只会调用一次;而对应的,
实际工作方法,例如RichMapFunction中的map(),在每条数据到来后都会触发一次调用。
来看一个例子:

import org.apache.flink.api.common.functions.RichMapFunction 
64 
65 
 
import org.apache.flink.configuration.Configuration 
import org.apache.flink.streaming.api.scala._ 
 
object RichFunctionTest{ 
  def main(args: Array[String]): Unit = { 
    val env = StreamExecutionEnvironment.getExecutionEnvironment 
    env.setParallelism(2) 
 
    env.fromElements( 
      Event("Mary", "./home", 1000L), 
      Event("Bob", "./cart", 2000L), 
      Event("Alice", "./prod?id=1", 5 * 1000L), 
      Event("Cary", "./home", 60 * 1000L) 
    ) 
      .map(new RichMapFunction[Event, Long]() { 
        //在任务生命周期开始时会执行open方法,在控制台打印对应语句 
        override def open(parameters: Configuration): Unit = { 
          println("索引为 " + getRuntimeContext.getIndexOfThisSubtask + " 的任务开
始") 
        } 
 
  // 将点击事件转换成长整型的时间戳输出 
        override def map(value: Event): Long = value.timestamp 
 
  //在任务声明周期结束时会执行close方法,在控制台打印对应语句 
        override def close(): Unit = { 
          println("索引为 " + getRuntimeContext.getIndexOfThisSubtask + " 的任务结
束") 
        } 
      }) 
      .print() 
 
    env.execute() 
  } 
} 

输出结果是:

索引为 0 的任务开始
索引为 1 的任务开始
1> 1000
2> 2000
2> 60000
1> 5000
索引为 0 的任务结束
索引为 1 的任务结束

一个常见的应用场景就是,如果我们希望连接到一个外部数据库进行读写操作,推荐的最
佳实践如下:

class MyFlatMap extends RichFlatMapFunction[IN,OUT]{ 
override def open(parameters: Configuration): Unit = { 
// 做一些初始化工作 
// 例如建立一个和MySQL的连接 
} 
override def flatMap(value: IN, out: Collector[OUT]): Unit = { 
// 对数据库进行读写 
} 
override def close(): Unit = { 
// 清理工作,关闭和MySQL数据库的连接。 
} 
} 

另外,富函数类提供了 getRuntimeContext()方法(我们在本节的第一个例子中使用了一
下),可以获取到运行时上下文的一些信息,例如程序执行的并行度,任务名称,以及状态
(state)。这使得我们可以大大扩展程序的功能,特别是对于状态的操作,使得Flink中的算子
具备了处理复杂业务的能力。

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用SparkSQL和Hive API,可以通过以下步骤实现用户自定义函数UDF)、聚合函数(UDAF)和表生成函数(UDTF): 1. 编写自定义函数的代码,例如: ``` // UDF def myUDF(str: String): Int = { str.length } // UDAF class MyUDAF extends UserDefinedAggregateFunction { override def inputSchema: StructType = StructType(StructField("value", StringType) :: Nil) override def bufferSchema: StructType = StructType(StructField("count", IntegerType) :: Nil) override def dataType: DataType = IntegerType override def deterministic: Boolean = true override def initialize(buffer: MutableAggregationBuffer): Unit = { buffer(0) = 0 } override def update(buffer: MutableAggregationBuffer, input: Row): Unit = { buffer(0) = buffer.getInt(0) + input.getString(0).length } override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = { buffer1(0) = buffer1.getInt(0) + buffer2.getInt(0) } override def evaluate(buffer: Row): Any = { buffer.getInt(0) } } // UDTF class MyUDTF extends GenericUDTF { override def initialize(args: Array[ConstantObjectInspector]): StructObjectInspector = { // 初始化代码 } override def process(args: Array[DeferedObject]): Unit = { // 处理代码 } override def close(): Unit = { // 关闭代码 } } ``` 2. 将自定义函数注册到SparkSQL或Hive中,例如: ``` // SparkSQL中注册UDF spark.udf.register("myUDF", myUDF _) // Hive中注册UDF hiveContext.sql("CREATE TEMPORARY FUNCTION myUDF AS 'com.example.MyUDF'") // Hive中注册UDAF hiveContext.sql("CREATE TEMPORARY FUNCTION myUDAF AS 'com.example.MyUDAF'") // Hive中注册UDTF hiveContext.sql("CREATE TEMPORARY FUNCTION myUDTF AS 'com.example.MyUDTF'") ``` 3. 在SQL语句中使用自定义函数,例如: ``` -- 使用SparkSQL中的UDF SELECT myUDF(name) FROM users -- 使用Hive中的UDF SELECT myUDF(name) FROM users -- 使用Hive中的UDAF SELECT myUDAF(name) FROM users GROUP BY age -- 使用Hive中的UDTF SELECT explode(myUDTF(name)) FROM users ``` 以上就是使用SparkSQL和Hive API实现用户自定义函数UDF、UDAF、UDTF)的步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值