Flink| ProcessFunction API(底层API)

watermark时间的获取

之前的转换算子 是无法访问事件的时间戳信息和 水位线 信息的。而这在一些应用场景下,极为重要。例如 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
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 )    onTimer(timestamp: Long, ctx: OnTimerContext, out: Collector[OUT]) 是一个回调函数。当之前注册的定时器触发时调用。
    参数 timestamp 为定时器所设定的触发的时间戳。 Collector 为输出结果的集合。 OnTimerContext 和processElement 的 Context 参数一样,提供了上下文的一些 信息,
    例如 定时器触发 的时间信息 事件时间或者处理时间 。
TimerService 和 定时器 Timers
Context和 OnTimerContext 所持有的 TimerService 对象拥有以下方法:
   currentProcessingTime(): Long 返回当前处理时间
   currentWatermark(): Long 返回当 前 watermark 的时间戳
   registerProcessingTimeTimer(timestamp: Long): Unit 会注册当前 key 的
     proces sing time 的 定时器 。当 processing time 到达定时时间时,触发 timer 。
   registerEventTimeTimer(timestamp: Long): Unit 会注册当前 key 的 event time定时器 。当 水位线 大于等于定时器注册的时间时,触发定时器执行回调函数。
   deleteProcessingTimeTimer(timestamp: Long): Unit 删除之前注册处理时间定时器。如果没有这个时间戳的定时器,则不执行。

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

package com.xxx.fink.api.windowapi

import com.xxx.fink.api.sourceapi.SensorReading
import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.util.Collector
import org.apache.flink.api.scala._
import org.apache.flink.streaming.api.TimeCharacteristic


/**
  * 1s之内温度连续上升就报警
  */
object ProcessFunctionTest {
  def main(args: Array[String]): Unit = {
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    val stream: DataStream[String] = env.socketTextStream("hadoop101", 7777)

    val dataStream: DataStream[SensorReading] = stream.map(data => {
      val dataArray: Array[String] = 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
      })


    val processedStream: DataStream[String] = dataStream.keyBy(_.id)
      .process(new TempIncreAlert())

    dataStream.print("Input data:")
    processedStream.print("process data:")

    env.execute("Window test")

  }


}

class TempIncreAlert() extends KeyedProcessFunction[String, SensorReading, String] {


  //温度连续上升,跟上一条数据做对比; 保存成当前状态
  //定义一个状态保存上一个数据的温度值
  lazy val lastTemp: ValueState[Double] = getRuntimeContext.getState(new ValueStateDescriptor[Double]("lastTemp", classOf[Double]))
  //定义一个状态,用来保存定时器的时间戳
  lazy val currentTimer: ValueState[Long] = getRuntimeContext.getState(new ValueStateDescriptor[Long]("currentTimer", classOf[Long]))

  override def processElement(value: SensorReading, ctx: KeyedProcessFunction[String, SensorReading, String]#Context, out: Collector[String]): Unit = {
    //先取出上一个温度值
    val preTemp = lastTemp.value()
    //更新温度值
    lastTemp.update(value.temperature)

    val curTimerTs = currentTimer.value()

    //温度上升且没有设过定时器,则注册定时器
    if (value.temperature > preTemp && curTimerTs == 0) {
      //如果温度下降,或是第一条数据,删除定时器并清空状态
      val timerTs = ctx.timerService().currentProcessingTime() + 10000L
      ctx.timerService().registerProcessingTimeTimer(timerTs)
      currentTimer.update(timerTs)
    } else if (preTemp > value.temperature || preTemp == 0.0) {
      // 温度上升且没有设过定时器,则注册定时器
      ctx.timerService().deleteProcessingTimeTimer(curTimerTs)
      currentTimer.clear()

    }
  }

  override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[String, SensorReading, String]#OnTimerContext, out: Collector[String]): Unit = {
    //输出报警信息
    out.collect(ctx.getCurrentKey + "温度连续上升")
    currentTimer.clear()
  }


}
Input data:> SensorReading(sensor_1,1547718199,35.0)
Input data:> SensorReading(sensor_1,1547718199,36.0)
process data:> sensor_1温度连续上升

侧输出 流( SideOutput)

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

 

import com.xxx.fink.api.sourceapi.SensorReading
import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, OutputTag, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.util.Collector
import org.apache.flink.api.scala._

/**
  * 侧输出流 代替split
  */
object SideOutputTest {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    val stream = env.socketTextStream("hadoop101", 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
    })
    val processedStream: DataStream[SensorReading] = dataStream.process(new FreezingAlert())
    processedStream.print("processed data: ")
    processedStream.getSideOutput(new OutputTag[String]("freezing alert")).print("alert data")

    env.execute("Window test")
  }
}

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

  lazy val alertOutPut: OutputTag[String] = new OutputTag[String]("freezing alert")

  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)
    }

  }
}

测试:

processed data: > SensorReading(sensor_1,1547718199,35.8)
alert data> freezing alert forsensor_6
alert data> freezing alert forsensor_7
processed data: > SensorReading(sensor_10,1547718205,38.1)

猜你喜欢

转载自www.cnblogs.com/shengyang17/p/12543524.html