flink的底层processionFunctionAPI

一、processionFunctionAPI产生背景

(1)背景:转换算子是无法访问事件的时间戳信息和水位线信息的。例如MapFunction这样的map转换算子就无法访问时间戳或者当前事件的事件时间。这样的算子和函数能够进行一些时间上的操作,但是不能获取算子当前的Processing Time或者是Watermark时间戳,调用起来简单但功能相对受限。flink提供Process Function可以访问时间戳、watermark以及注册定时事件。还可以输出特定的一些事件。用Process Function来构建事件驱动的应用以及实现自定义的业务逻辑(使用之前的window函数和转换算子无法实现)

(2)作用:

  1、访问events

  2、访问状态State(容错,一致性,但是仅仅是在keyed流上)

  3、计时器,也仅仅在keyed流上。

(3)Process Function的类型:

  1、ProcessFunction dataStream

  2、KeyedProcessFunction 用于KeyedStream,keyBy之后的流处理

  3、CoProcessFunction 用于connect连接的流

  4、ProcessJoinFunction 用于join流操作

  5、BroadcastProcessFunction 用于广播

  6、KeyedBroadcastProcessFunction keyBy之后的广播

  7、ProcessWindowFunction 窗口增量聚合

  8、ProcessAllWindowFunction 全窗口聚合

  注意:上述8个函数各有侧重,但核心功能比较相似,主要包括两点:状态:我们可以在这些函数中访问和更新Keyed State 。定时器(Timer):像定闹钟一样设置定时器,我们可以在时间维度上设计更复杂的业务逻辑。

二、KeyedProcessFunction实例

  KeyedProcessFunction用来操作KeyedStream。KeyedProcessFunction会处理流的每一个元素,输出为0个、1个或者多个元素。所有的Process Function都继承自RichFunction接口,所以都有open()、close()和getRuntimeContext()等方法。ProcessFunction有两个重要的接口processElement和onTimer。

(1)processElement函数:processElement(v: IN, ctx: Context, out: Collector[OUT])

  流中的每一个元素都会调用这个方法,调用结果将会放在 Collector 数据类型中输出。Context可以访问元素的时间戳,元素的 key,以及 TimerService 时间服务。Context还可以将结果输出到别的流(side outputs)。

(2)onTimer(timestamp: Long, ctx: OnTimerContext, out: Collector[OUT])

  是一个回调函数。当之前注册的定时器触发时调用。参数 timestamp 为定时器所设定的触发的时间戳。Collector 为输出结果的集合。OnTimerContext 和 processElement 的 Context 参数一样,提供了上下文的一些信息,例如定时器触发的时间信息(事件时间或者处理时间)。

  1、在processElement方法中通过Context注册一个未来的时间戳t。这个时间戳的语义可以是Processing Time,也可以是Event Time,根据业务需求来选择。

  2、在onTimer方法中实现一些逻辑,到达t时刻,onTimer方法被自动调用。

(3)TimerService 和 定时器(Timers)

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

  1、currentProcessingTime(): Long 返回当前处理时间

  2、currentWatermark(): Long 返回当前watermark的时间戳

  3、registerProcessingTimeTimer(timestamp: Long): Unit 会注册当前key的processing time的定时器。当processing time到达定时时间时,触发timer。

  4、registerEventTimeTimer(timestamp: Long): Unit 会注册当前key的event time 定时器。当水位线大于等于定时器注册的时间时,触发定时器执行回调函数。

  5、deleteProcessingTimeTimer(timestamp: Long): Unit 删除之前注册处理时间定时器。如果没有这个时间戳的定时器,则不执行。

  6、deleteEventTimeTimer(timestamp: Long): Unit 删除之前注册的事件时间定时器,如果没有此时间戳的定时器,则不执行。

  当定时器timer触发时,会执行回调函数onTimer()。注意定时器timer只能在keyed streams上面使用。

(4)KeyedProcessFunction如何操作KeyedStream

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

 1 package study
 2 
 3 import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
 4 import org.apache.flink.streaming.api.functions.KeyedProcessFunction
 5 import org.apache.flink.streaming.api.scala._
 6 import org.apache.flink.streaming.api.windowing.time.Time
 7 import org.apache.flink.util.Collector
 8 
 9 object ProcessFunctionTest {
10   def main(args: Array[String]): Unit = {
11     val env = StreamExecutionEnvironment.getExecutionEnvironment
12     env.setParallelism(1)
13     val stream = env.socketTextStream("note01", 7777)
14     stream.map(data => {
15       val dataArray = data.split(",")
16       SensorReading(dataArray(0).trim, dataArray(1).trim.toLong, dataArray(2).trim.toDouble)
17     }).keyBy(_.id)
18       .process(new TimeIncreaseWarning()).print()
19     env.execute()
20   }
21 
22 }
23 
24 //参数 key的数据类型,输入数据类型,输出数据类型
25 class TimeIncreaseWarning() extends KeyedProcessFunction[String, SensorReading, String] {
26   //状态编程,保存之前的温度
27   lazy val lastTemp: ValueState[Double] = getRuntimeContext.getState(new ValueStateDescriptor[Double]("lastTemp", classOf[Double]))
28   //保存注册定时器的时间戳
29   lazy val currentTimer: ValueState[Long] = getRuntimeContext.getState(new ValueStateDescriptor[Long]("currentTimer", classOf[Long]))
30 
31   override def processElement(value: SensorReading, ctx: KeyedProcessFunction[String, SensorReading, (String, String)]#Context, out: Collector[(String, String)]): Unit = {
32     //取出上一次温度
33     val prevTemp = lastTemp.value()
34     //将当前温度更新到上一次的温度这个变量中
35     lastTemp.update(value.temperature)
36 
37     val curTimerTimestamp = currentTimer.value()
38 
39     if (prevTemp == 0.0 || value.temperature < prevTemp) {
40       // 温度下降或者是第一个温度值,删除定时器
41       ctx.timerService().deleteEventTimeTimer(curTimerTimestamp)
42       //清空状态变量
43       currentTimer.clear()
44     } else if (value.temperature > prevTemp && curTimerTimestamp == 0) {
45       // 温度上升且我们并没有设置定时器
46       val timerTs = ctx.timerService().currentProcessingTime() + 1000
47       ctx.timerService().registerProcessingTimeTimer(timerTs)
48 
49       currentTimer.update(timerTs)
50     }
51   }
52 
53   override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[String, SensorReading, (String, String)]#OnTimerContext, out: Collector[(String, String)]): Unit = {
54     out.collect("传感器id为: " + ctx.getCurrentKey + "的传感器温度值已经连续1s上升了。")
55     currentTimer.clear()
56   }
57 }

(5)Emitting to Side Outputs(侧输出)

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

package study

import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.util.Collector

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

    val stream = env.socketTextStream("localhost", 7777)

    val dataStream = stream.map(data => {
      val dataArray = data.split(",")
      SensorReading(dataArray(0).trim, dataArray(1).trim.toLong, dataArray(2).trim.toDouble)
    })
      .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
        override def extractTimestamp(element: SensorReading): Long = element.timeStamp * 1000
      })

    //没有keyby操作,不用keyProcessFunction
    val processedStream = dataStream.process(new FreezingAlert())
    processedStream.print("processed data")
    //侧输出流的输出方式
    processedStream.getSideOutput( new OutputTag[String]("freezing alert") ).print("alert data")
  }
}

// 冰点报警,如果小于32F,输出报警信息到侧输出流
class FreezingAlert() extends ProcessFunction[SensorReading, SensorReading] {

  //盖戳
  lazy val alertOutput: OutputTag[String] = new OutputTag[String]("freezing alter")

  override def processElement(value: SensorReading, ctx: ProcessFunction[SensorReading, SensorReading]#Context, out: Collector[SensorReading]): Unit = {
    if( value.temperature < 32.0 ){
      //侧输出流采用上下文
      ctx.output( alertOutput, "freezing alert for " + value.id )
    } else {
      out.collect( value )
    }
  }
}

(6)CoProcessFunction

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

posted on 2020-06-12 17:25  hdc520  阅读(550)  评论(0编辑  收藏  举报

导航