FLink学习笔记:08-Flink 的ProcessFunction API操作

文章目录

  • 概述
  • KeyedProcessFunction
    • TimerService和定时器
    • ProcessFunction实现侧输出流

概述

Flink中的转换算子是无法访问事件的时间戳信息和水位线信息,但是在一些应用场景下,这些信息却十分重要。基于此,DataStream API提供了一系列的Low-Level转换算子。可以访问时间戳、watermark以及注册定时事件。还可以输出特定的一些事件,例如超时事件等。Process Function用来构建时间驱动的应用以及实现用户自定义的业务逻辑。

Flink 提供了8个Process Function

  • ProcessFunction
  • KeyedProcessFunction
  • CoProcessFunction
  • ProcessJoinFunction
  • BroadcastProcessFunction
  • KeyedBroadcastProcessFunction
  • ProcessWindowFunction
  • ProcessAllWindowFunction

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参数一样,提供了上下文的一些信息,例如定时器 触发的时间信息(事件时间或者处理时间)。

TimerService和定时器

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 删除之前注册的事件时间定时 器,如果没有此时间戳的定时器,则不执行。

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

Tips2: registerEventTimeTimer要和deleteEventTimeTimer一一对应,不能和processingTimeTimer的操作混用。

示例:监控温度传感器的温度值,如果温度值在一秒钟之内(processing time)连 续上升,则报警,代码如下:

package com.hjt.yxh.hw.processfunction

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

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

object KeyedProcessFunctionTest {

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

    val dataStream = env.socketTextStream("192.168.0.52",7777)

    //需求,如果同一个传感器的温度连续10秒上升,需要发出预警信息
    val sensorStream = dataStream
      .filter(_.nonEmpty)
      .map(data=>{
        val array = data.split(",")
        SensorReading(array(0),array(1).toLong,array(2).toDouble)
      })
      .keyBy(data=>{
        data.id
      })
      .process(new WarnProcessFunction(10000L))
      .print()

    env.execute("KeyedProcessFunction job test")
  }
}

class WarnProcessFunction(interval:Long) extends KeyedProcessFunction[String,SensorReading,String]{
  //保存上一个温度的状态
  lazy val lastTempState:ValueState[Double] = getRuntimeContext.getState(new ValueStateDescriptor[Double]("lastTempState",classOf[Double]))
  //保存定时器的时间戳状态
  lazy val timerTsState:ValueState[Long] = getRuntimeContext.getState(new ValueStateDescriptor[Long]("timerTsState",classOf[Long]))

  override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[String, SensorReading, String]#OnTimerContext, out: Collector[String]): Unit = {
    out.collect("传感器:"+ctx.getCurrentKey + "的温度连续" + interval/1000 + "秒连续上升")
    timerTsState.clear()
  }

  override def processElement(value: SensorReading, ctx: KeyedProcessFunction[String, SensorReading, String]#Context, out: Collector[String]): Unit = {
    println("ID" + value.id + "溫度" + value.temperature + "時間"+value.temperature)
    //先取出状态
    val lastTemp = lastTempState.value()
    val timerTs = timerTsState.value()

    //如果温度上升,且没有定时器
    if(value.temperature>lastTemp && timerTs == 0){
      val ts = ctx.timerService().currentProcessingTime() + interval
      ctx.timerService().registerProcessingTimeTimer(ts)
      timerTsState.update(ts)
    }
    //如果温度下降,删除定时器
    else if (value.temperature<lastTemp && timerTs >0){
      ctx.timerService().deleteProcessingTimeTimer(timerTs)
      timerTsState.clear()
    }
    //更新温度值
    lastTempState.update(value.temperature)
  }
}

ProcessFunction实现侧输出流

我们之前在使用DataStream API的算子输出时,使用split将一条流分成多条流,这些流的数据类型也都相同。但是在使用这一API时,提示该API将会被移除,所以我们可以使用ProcessFunction来实现这一目标。

package com.hjt.yxh.hw.processfunction

import com.hjt.yxh.hw.apitest.SensorReading
import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
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 datastream = env.socketTextStream("192.168.0.52",7777)
        .filter(_.nonEmpty)
        .map(data=>{
          val array = data.split(",")
          SensorReading(array(0),array(1).toLong,array(2).toDouble)
        })

    val highTempStream = datastream
      .process( new SplitTempProcessor(30.0,"Low"))

    highTempStream.print("high")
    val lowTempStream = highTempStream.getSideOutput(new OutputTag[SensorReading]("Low"))
    lowTempStream.print("low")

    env.execute("side output test")
  }
}

class SplitTempProcessor(threshold: Double,sideOutputTag: String) extends ProcessFunction[SensorReading,SensorReading]{
  override def processElement(value: SensorReading, ctx: ProcessFunction[SensorReading, SensorReading]#Context, out: Collector[SensorReading]): Unit = {
    if (value.temperature > threshold) {
      //如果当前温度值大于30,那么输出到主流
      out.collect(value)
    }
    else{
      //温度低于30度,输出到侧输出流
      ctx.output[SensorReading](new OutputTag[SensorReading](sideOutputTag),value)
    }

  }
}

你可能感兴趣的:(FLink,大数据,flink,学习,大数据)