08ProcessFunction API(底层 API)——Flink

之前学习的转换算子是无法访问事件的时间戳信息和水位线信息的,基于此,DataStream API 提供了一系列的 Low-Level 转换算子。可以访问时间 戳、watermark 以及注册定时事件。还可以输出特定的一些事件,

Flink SQL 就是使用 Process Function 实 现的。 Flink 提供了 8 个 Process Function:

ProcessFunction 
KeyedProcessFunction  // keyby之后 的 可以操作keyStream
CoProcessFunction    
ProcessJoinFunction  
BroadcastProcessFunction 
KeyedBroadcastProcessFunction 
ProcessWindowFunction  // 全窗口函数
ProcessAllWindowFunction

KeyedProcessFunction 用来操作 KeyedStream。KeyedProcessFunction 会处理流 的每一个元素,而 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 参数一样,提供了上下文的一些信息,例如定时器 触发的时间信息(事件时间或者处理时间)。

 

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 上面使用。

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

object ProcessFunctionTestTwo {
  case class SensorReading(id: String, timestamp: Long, temperature: Double)
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

    env.enableCheckpointing(60000)
    env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.AT_LEAST_ONCE)
    env.getCheckpointConfig.setCheckpointTimeout(100000)
    env.getCheckpointConfig.setFailOnCheckpointingErrors(false)
    //    env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
    env.getCheckpointConfig.setMinPauseBetweenCheckpoints(100)
    env.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION)

    env.setRestartStrategy(RestartStrategies.failureRateRestart(3, org.apache.flink.api.common.time.Time.seconds(300), org.apache.flink.api.common.time.Time.seconds(10)))

    //    env.setStateBackend( new RocksDBStateBackend("") )

    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后调用process处理时间报警问题
    // keyby后则继承KeyedProcessFunction, 没有keyby则继承ProcessFunction以此类推
    val processedStream = dataStream.keyBy(_.id)
      .process( new TempIncreAlert() )

    dataStream.print("input data")
    processedStream.print()
    env.execute("process function test")
  }
}
// 输入是字符串,输出的报警信息也用字符串
class TempIncreAlert() extends KeyedProcessFunction[String, SensorReading, String]{

  /**2、因为是连续上升,所以定义一个状态,用来保存上一个数据的温度值 (如果一直大就是连续上升)
    * ValueState这是flink定义状态的类型,通过getRuntimeContext运行上下文获取状态句柄
    * 里面传的是一个描述器(名字,类型)
    */
  lazy val lastTemp: ValueState[Double] = getRuntimeContext.getState( new ValueStateDescriptor[Double]("lastTemp", classOf[Double]) )
  // 6、定义一个状态,用来保存定时器的时间戳
  lazy val currentTimer: ValueState[Long] = getRuntimeContext.getState( new ValueStateDescriptor[Long]("currentTimer", classOf[Long]) )

  // 1、要实现这个方法 因为所有来的元素都要经过这个方法
  override def processElement(value: SensorReading, ctx: KeyedProcessFunction[String, SensorReading, String]#Context, out: Collector[String]): Unit = {
    // 3、先取出上一个温度值
    val preTemp = lastTemp.value()
    // 4、更新温度值(更新的值从传入的数据中取)
    lastTemp.update( value.temperature )
    // 7、获取当前时间戳
    val curTimerTs = currentTimer.value()

    if( value.temperature < preTemp || preTemp == 0.0 ){
      // 8、如果温度下降,或是第一条数据,删除定时器并清空状态
      ctx.timerService().deleteProcessingTimeTimer( curTimerTs )
      currentTimer.clear()  // 情况状态避免撑爆内存
    } else if ( value.temperature > preTemp && curTimerTs == 0 ){ // 当前温度大于上一次的
      // 5、温度上升且没有设过定时器,则注册定时器
      val timerTs = ctx.timerService().currentProcessingTime() + 5000L
      ctx.timerService().registerProcessingTimeTimer( timerTs ) // 这里给的timerTs是时间戳
      currentTimer.update( timerTs )
    }
  }

  /** 9、上面定义了定时器,那么定时器触发之后需要做什么操作呢,定时器是在回调函数里面调出来
    */
  override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[String, SensorReading, String]#OnTimerContext, out: Collector[String]): Unit = {
    // 只要触发就输出报警信息
    out.collect( ctx.getCurrentKey + " 温度连续上升" ) // 通过上下文获取key
    currentTimer.clear()
  }
}

 

侧输出流

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

object SideOutputTest {
  case class SensorReading(id: String, timestamp: Long, temperature: Double)
  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了,则后面直接继承ProcessFunction
    val processedStream = dataStream
      .process( new FreezingAlert() )

    // 这里打印出来的是处理之后的主流
    processedStream.print("processed data")
    // 获取侧输出流,然后打印
    processedStream.getSideOutput( new OutputTag[String]("freezing alert") ).print("alert data")

    env.execute("side output 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( new OutputTag[String]( "freezing alert" ), "freezing alert for " + value.id )
      ctx.output( alertOutput, "freezing alert for " + value.id )
    }
    out.collect( value ) // 直接输出到主流
  }
}

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值