Flink-5(ProcessFunction)

Flink-5


ProcessFunction是Flink中用户可以操作的最底层的API, ProcessFunction的运行机制可以近似理解为 FlatMapFunction,同样是对流中的每个元素进行处理,每个输入的元素可以对应零到多个的输出;

ProcessFunction中,可以获取流的以下基本构件:

  • events:流中的元素
  • state:状态(仅用于keyed stream
  • timers:定时器,支持事件时间和处理时间,仅用于keyed stream

Flink提供了8个Process Function:

  • ProcessFunction:dataStream
  • KeyedProcessFunction:用于KeyedStream,keyBy之后的流处理
  • CoProcessFunction:用于connect连接的流
  • ProcessJoinFunction:用于join流操作
  • BroadcastProcessFunction:用于广播
  • KeyedBroadcastProcessFunction:keyBy之后的广播
  • ProcessWindowFunction:窗口增量聚合
  • ProcessAllWindowFunction:全窗口聚合

在数据流中,通过调用process()函数,传入对应的ProcessFunction即可

下面通过案例演示用法,案例需求:对传感器传回的温度进行监控,如果温度10S内持续上升,则触发报警。每当接收到一条记录后,输出当前数据记录相对于上一条记录的温度变化值

案例代码:

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

object ProcessFunctionDemo1 {
  def main(args: Array[String]): Unit = {
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    val source: DataStream[String] = env.socketTextStream("192.168.226.10", 7777)
    source
      .process(ParseSensorReading) // 前几篇中的flatMap和map函数,使用process实现
      .keyBy("id")
      .process(TemperatureMonitor) // keyBy之后,需要使用KeyedProcessFunction
      .print("test")

    env.execute("blogTest")
  }

  private object ParseSensorReading extends ProcessFunction[String, SensorReading] {
    override def processElement(value: String, ctx: ProcessFunction[String, SensorReading]#Context, out: Collector[SensorReading]): Unit = {
      if (value.matches("^sensor_\\d,\\d{13},\\d{2}(\\.\\d+)?$")) {
        val fields: Array[String] = value.split(",")
        // out.collect的内容会被作为一条记录返回
        out.collect(SensorReading(fields(0), fields(1).toLong, fields(2).toDouble))
      }
    }
  }

  private object TemperatureMonitor extends KeyedProcessFunction[Tuple, SensorReading, Double] {
    // 记录回调函数设定的时间
    private var tsTimer: ValueState[Long] = _
    // 记录上一条记录的温度
    private var lastTemperature: ValueState[Double] = _

	// 元素处理前,对上述两个状态初始化
    override def open(parameters: Configuration): Unit = {
      tsTimer = getRuntimeContext.getState(new ValueStateDescriptor[Long]("registeredTimer", classOf[Long]))
      lastTemperature = getRuntimeContext.getState(new ValueStateDescriptor[Double]("lastTemperature", classOf[Double]))
    }

    override def processElement(value: SensorReading, ctx: KeyedProcessFunction[Tuple, SensorReading, Double]#Context, out: Collector[Double]): Unit = {
      if (lastTemperature.value() != null) { // 如果不是第一条传入的数据
        // 当前未注册回调函数,且当前温度大于上一条数据的温度
        if (tsTimer.value() == null && value.temperature > lastTemperature.value()) {
          // 记录当前处理时间,如果不记录该时间,后面两行分别获取当前处理时间可能会导致记录时间和实际注册时间不符
          val registerTimer: Long = ctx.timerService().currentProcessingTime() + (10 * 1000)
          // 注册回调函数
          ctx.timerService().registerProcessingTimeTimer(registerTimer)
          // 更新触发时间
          tsTimer.update(registerTimer)
        // 当前已经注册了回调函数,且当前温度不大于上一条数据的温度,则取消回调函数
        } else if (tsTimer.value() != null && value.temperature <= lastTemperature.value())
          ctx.timerService().deleteProcessingTimeTimer(tsTimer.value())
      }
      // 如果不是第一条传入的数据,则输出当前温度与上一条数据温度的差值
      if (lastTemperature.value() != null) out.collect(value.temperature - lastTemperature.value())
      // 记录当前温度
      lastTemperature.update(value.temperature)
    }

    override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Tuple, SensorReading, Double]#OnTimerContext, out: Collector[Double]): Unit = {
      println("警告:" + ctx.getCurrentKey.toString + "温度持续10S上升!")
      // 触发后清空计数,后续的触发才能再记录温度
      tsTimer.clear()
    }
  }
}

可以看到定时器的定义和工作机制和前面的Evictor很相似,只是没有在两个函数中区分ProcessTime和EventTime,而是统一在onTimer函数中添加回调函数的逻辑

使用ProcessFunction对数据流分流:

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
import transform.SensorReading

object ProcessFunctionDemo2 {
  def main(args: Array[String]): Unit = {
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    val source: DataStream[String] = env.socketTextStream("192.168.226.10", 7777)
    val res: DataStream[SensorReading] = source.filter(_.matches("^sensor_\\d,\\d{13},\\d{2}(\\.\\d+)?$"))
      .map(line => {
        val fields: Array[String] = line.split(",")
        SensorReading(fields(0), fields(1).toLong, fields(2).toDouble)
      })
      .keyBy("id")
      .process(SplitProcess)

    res.print("normal")
    // 获取侧输出流,java创建OutputTag时后面的大括号不能省略
    res.getSideOutput(new OutputTag[SensorReading]("high")).print("high")
    res.getSideOutput(new OutputTag[SensorReading]("low")).print("low")

    env.execute("blogTest")
  }

  private object SplitProcess extends KeyedProcessFunction[Tuple, SensorReading, SensorReading] {
    override def processElement(value: SensorReading, ctx: KeyedProcessFunction[Tuple, SensorReading, SensorReading]#Context, out: Collector[SensorReading]): Unit = {
      // 温度大于37度的放入侧输出流
      if (value.temperature > 37) ctx.output(new OutputTag[SensorReading]("high"), value)
      // 温度小于35度的放入另一个侧输出流
      else if (value.temperature < 35) ctx.output(new OutputTag[SensorReading]("low"), value)
      // 其余正常输出
      else out.collect(value)
    }
  }
}

使用process输出到侧输出流可以代替split-select用来分流。

Flink中,可以使用ProcessFunction来进行流的分流操作。通过扩展ProcessFunction<JSONObject, JSONObject>类,可以实现对流数据的处理和分流操作。具体实现的代码如下所示: ```java public class MyProcessFunction extends ProcessFunction<JSONObject, JSONObject> { @Override public void processElement(JSONObject value, Context ctx, Collector<JSONObject> out) throws Exception { // 在这里可以对输入的流数据进行处理 // 然后根据需要将处理结果发送到不同的分流输出 if (value.containsKey("type") && value.getString("type").equals("A")) { ctx.output(new OutputTag<JSONObject>("output-A"){}, value); } else if (value.containsKey("type") && value.getString("type").equals("B")) { ctx.output(new OutputTag<JSONObject>("output-B"){}, value); } else { ctx.output(new OutputTag<JSONObject>("output-other"){}, value); } } } ``` 在上述代码中,我们首先扩展了ProcessFunction<JSONObject, JSONObject>类,并重写了processElement方法。在该方法中,我们可以对输入的JSONObject数据进行处理,并根据一定的条件将数据发送到不同的分流输出。在这个例子中,如果数据的type字段为"A",则将数据发送到名为"output-A"的分流输出中;如果type字段为"B",则发送到"output-B";否则发送到"output-other"。具体的分流输出可以在Flink程序中进行定义和处理。 需要注意的是,上述代码仅是一个简单的示例,实际使用时需要根据具体的业务需求进行适当的修改和扩展。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Flink 使用 ProcessFunction 处理时间乱序数据](https://blog.csdn.net/zx711166/article/details/123730586)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Flink实时处理并将结果写入ElasticSearch实战](https://blog.csdn.net/weixin_44516305/article/details/90258883)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值