Flink ProcessFunction API

Flink提供的转换算子是无法访问事件的时间戳信息和水位线信息的。而这在一些应用场景下,极为重要。例如MapFunction这样的map转换算子就无法访问时间戳或者当前事件的事件时间。
基于此,DataStream API提供了一系列的Low-Level转换算子。可以访问时间戳、watermark以及注册定时事件。还可以输出特定的一些事件,例如超时事件等。Process Function用来构建事件驱动的应用以及实现自定义的业务逻辑(使用之前的window函数和转换算子无法实现)。例如,Flink SQL就是使用Process Function实现的。
Flink提供了8个Process Function:

ProcessFunction
KeyedProcessFunction
CoProcessFunction
ProcessJoinFunction
BroadcastProcessFunction
KeyedBroadcastProcessFunction
ProcessWindowFunction
ProcessAllWindowFunction

1. KeyedProcessFunction

KeyedProcessFunction用来操作KeyedStream。KeyedProcessFunction会处理流的每一个元素,输出为0个、1个或者多个元素。所有的Process Function都继承自RichFunction接口,所以都有open()、close()和getRuntimeContext()等方法。而KeyedProcessFunction[KEY, IN, OUT]还额外提供了两个方法:

processElement(v: IN, ctx: Context, out: Collector[OUT]), 流中的每一个元素都会调用这个方法,调用结果将会放在Collector数据类型中输出。Context可以访问元素的时间戳,元素的key,以及TimerService时间服务。Context还可以将结果输出到别的流(side outputs)。
onTimer(timestamp: Long, ctx: OnTimerContext, out: Collector[OUT])是一个回调函数。当之前注册的定时器触发时调用。参数timestamp为定时器所设定的触发的时间戳。Collector为输出结果的集合。OnTimerContext和processElement的Context参数一样,提供了上下文的一些信息,例如定时器触发的时间信息(事件时间或者处理时间)。

2. TimerService 和 定时器(Timers)

Context和OnTimerContext所持有的TimerService对象拥有以下方法:

currentProcessingTime(): Long 返回当前处理时间
currentWatermark(): Long 返回当前watermark的时间戳
registerProcessingTimeTimer(timestamp: Long): Unit 会注册当前key的processing time的定时器。当processing time到达定时时间时,触发timer。
registerEventTimeTimer(timestamp: Long): Unit 会注册当前key的event time 定时器。当水位线大于等于定时器注册的时间时,触发定时器执行回调函数。
deleteProcessingTimeTimer(timestamp: Long): Unit 删除之前注册处理时间定时器。如果没有这个时间戳的定时器,则不执行。
deleteEventTimeTimer(timestamp: Long): Unit 删除之前注册的事件时间定时器,如果没有此时间戳的定时器,则不执行。

当定时器timer触发时,会执行回调函数onTimer()。注意定时器timer只能在keyed streams上面使用。
下面举个例子说明KeyedProcessFunction如何操作KeyedStream。

需求:监控温度传感器的温度值,如果温度值在一秒钟之内(processing time)连续上升,则报警。

import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
import org.apache.flink.api.java.tuple.Tuple
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector

object ProcessFunctionTest {
    def main(args: Array[String]): Unit = {
        val env = StreamExecutionEnvironment.getExecutionEnvironment
        env.setParallelism(1)

        val inputStream = env.socketTextStream("hadoop", 7777)
        val dataStream = inputStream
            .map(data => {
                val dataArray = data.split(",")
//                println(dataArray(2).toDouble.getClass)
                SensorReading4(dataArray(0), dataArray(1).toLong, dataArray(2).toDouble)
            })

        // 检测每个传感器温度是否连续上升,在10秒之内
        val warningStream = dataStream
            .keyBy("id")
            .process(new TempIncreWarning(10000L))

        warningStream.print()

        env.execute("XXXXXX")


    }


}

// 自定义KeyedProcessFunction 泛型 key的类型, 输入类型  输出类型
class TempIncreWarning(interal: Long) extends KeyedProcessFunction[Tuple, SensorReading4, String] {
    // 由于要跟之前的温度做比较,所以将上一个温度保存成状态
    lazy val lastTempState: ValueState[Double] = getRuntimeContext.getState(new ValueStateDescriptor[Double]("lastTemp", classOf[Double]))
    // 为了删除定时器,还需要保存定时器的时间戳
    lazy val curTimerTsState: ValueState[Long] = getRuntimeContext.getState(new ValueStateDescriptor[Long]("cur-timer-ts", classOf[Long]))

    override def processElement(value: SensorReading4, context: KeyedProcessFunction[Tuple, SensorReading4, String]#Context, collector: Collector[String]): Unit = {
        // 首先取出状态
        val lastTemp = lastTempState.value()
        val curTimerTs = curTimerTsState.value()

        // 将上次温度值的状态更新为当前数据温度值
        lastTempState.update(value.temperature)

        // 判断当前温度值,如果比之前温度高,并且没有定时器的话,注册10秒后的定时器
        if (value.temperature > lastTemp && curTimerTs == 0) {
            val ts = context.timerService().currentProcessingTime() + interal
            context.timerService().registerProcessingTimeTimer(ts)
            curTimerTsState.update(ts)
        }
        // 如果温度下降就删除定时器
        else if (value.temperature < lastTemp) {
            context.timerService().deleteProcessingTimeTimer(curTimerTs)
            // 清空状态
            curTimerTsState.clear()
        }


    }

    // 10秒内温度没有下降,定时器到达触发的时间
    override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Tuple, SensorReading4, String]#OnTimerContext, out: Collector[String]): Unit = {
        //定时器触发
        out.collect("温度值连续" + interal / 1000 + "秒上升")
        // 清空状态
        curTimerTsState.clear()
    }
}


case class SensorReading4(id: String, timestamp: Long, temperature: Double)

3. 侧输出流(SideOutput)

大部分的DataStream API的算子的输出是单一输出,也就是某种数据类型的流。除了split算子,可以将一条流分成多条流,这些流的数据类型也都相同。process function的side outputs功能可以产生多条流,并且这些流的数据类型可以不一样。一个side output可以定义为OutputTag[X]对象,X是输出流的数据类型。process function可以通过Context对象发射一个事件到一个或者多个side outputs。

下面是一个示例程序:

import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector

object SideOutputTest {
    def main(args: Array[String]): Unit = {

        val env = StreamExecutionEnvironment.getExecutionEnvironment

        val inputStream = env.socketTextStream("hadoop", 7777)
        val dataStream = inputStream.map {
            data =>
                val dataArray = data.split(",")
                SensorReading5(dataArray(0), dataArray(1).toLong, dataArray(2).toDouble)
        }
        // 用ProcessFunction的侧输出流实现分流
        val hightempStream = dataStream.process(new SplitTempProcessor(30.0))

        val lowTempStream = hightempStream.getSideOutput(new OutputTag[(String, Double, Long)]("low-temp"))

        // 打印输出
        hightempStream.print("high")
        lowTempStream.print("low")

        env.execute("XXXX")
    }
}

// 自定义ProcessFunction,用于区分高低温的数据
class SplitTempProcessor(threshold: Double) extends ProcessFunction[SensorReading5, SensorReading5] {
    override def processElement(value: SensorReading5, context: ProcessFunction[SensorReading5, SensorReading5]#Context, out: Collector[SensorReading5]): Unit = {
        // 判断当前数据的温度值,如果大于阈值,输出到主流,否则输出到低温流
        if (value.temperature > threshold) {
            out.collect(value)
        } else {
            context.output(new OutputTag[(String, Double, Long)]("low-temp"), (value.id, value.temperature, value.timestamp))
        }
    }
}


case class SensorReading5(id: String, timestamp: Long, temperature: Double)

4. CoProcessFunction

对于两条输入流,DataStream API提供了CoProcessFunction这样的low-level操作。CoProcessFunction提供了操作每一个输入流的方法: processElement1()和processElement2()。
类似于ProcessFunction,这两种方法都通过Context对象来调用。这个Context对象可以访问事件数据,定时器时间戳,TimerService,以及side outputs。CoProcessFunction也提供了onTimer()回调函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值