f基础入门4--flink状态管理和ProcessFunction API

Flink状态管理

flink对自己的定位是,分布式框架和大数据处理引擎,对数据流做有状态的计算。
状态可以认为是一个本地变量。task执行的时候就是在slot上执行的一个线程,执行过程中用到的所有数据,保存在当前的线程所独享的内存中去。所以就是持有的一个变量而已。只是这么认为,但是并不是一个真正的变量。但是我们是一个分布式系统,flink在运行过程中会自动做状态管理。

在这里插入图片描述

状态就是某一个任务保存的数据。每一个状态都是和特定的任务和特定的算子关联在一起的。就是我们在代码中定义的每一步操作,在里面有自己的一个状态。

状态使用前,先注册状态。也就是在使用前,在环境中,先进性注册状态。就是相当于把状态定义出来,在运行时的环境下,获取一下状态。具体来讲,就是给状态一个名字,然后在运行时上下文里面,具体的数据类型也要定义出来,这样的话,flink就能找到这个状态,并知道怎么做了。

总之,算子的状态有两种,一种是算子状态,一种是键控状态。

算子状态

一般化的,没有分组,直接定义状态。当前并行的算子任务,都能访问到同样的状态。子任务里面的数据的状态相同。

作用范围,类似于变量的作用域

在这里插入图片描述

图中不同slot里面的相同算子的任务,二者之间,不能直接访问,要访问的话,就得做序列化和反序列化。要进行做网络传输操作。

Flink 为算子状态提供三种基本数据结构

1、列表状态(List state)
将状态表示为一组数据的列表。

2、联合列表状态(Union list state)
也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保 存点(savepoint)启动应用程序时如何恢复。
3、广播状态(Broadcast state)
如果一个算子有多项任务,而它的每项任务状态又都相同(也就是不同slot中的同一个算子的状态相同),那么这种特殊情况最适合应 用广播状态。

键控状态

我们要做开窗,要做聚合的话,都得先进行keyby。接下来考虑的状态,也是和分组相关。都是分了组讨论的,不会讨论所有的。所以,一般用的是键控状态。
针对每一个key,做访问和维护。每个任务中每个key中都有一个状态。,针对每一个key,都会保存一份独有的状态。

在这里插入图片描述

ValueState

case class SensorReading3 (id :String , timestamp: Long ,tempreture:Double)

拿mapfunction做举例,都是一样的,里面的代码。

ValueState[T]保存单个的值,值的类型为 T。
	get 操作: ValueState.value()   //因为只有一个值,不用get,直接value。
	
	set 操作: ValueState.update(value: T)

主要代码如下:


//keyed state测试: 必须定义在RichFunction中,因为需要运行时上下文。
class MyMapperFunction extends  RichMapFunction[SensorReading3,String] {

    //定义状态的变量,放在最上面,因为是全局的,所有的方法中都要用到,注意变量是可变的,用var不是val。

  var valuestate : ValueState[Double] = _


  override def open(parameters: Configuration): Unit = {


      //从--获取上下文--这个方法中--获取状态--这个方法获取状态。
      //按住Ctrl,点进去getstate这个方法中,然后看到  getState(stateProperties: ValueStateDescriptor[T])
      // 然后点  ValueStateDescriptor[T] 进去,看看这个是什么类型。
      //然后点进去,看到构造方法。
      //   def this(name: String, typeClass: Class[T]) { this()
     //        super (name, typeClass, null)
    //   }
    // 因为按住ctrl + f12,只有这个构造方法是推荐的,其他的构造方法不推荐。那么就用这个构造方法了。

    valuestate =
      getRuntimeContext.getState(new ValueStateDescriptor[Double]("valuestate",classOf[Double]))

  }


  override def map(value: SensorReading3): String = {

      //获取上一次状态保存的值。
    val myvalue = valuestate.value()

     //更新状态中的值。
     valuestate.update(value.tempreture)

     value.id
  }

}


ListState

拿mapfunction做举例,都是一样的,里面的代码。

case class SensorReading3 (id :String , timestamp: Long ,tempreture:Double)

ListState[T]保存一个列表,列表里的元素的数据类型为 T。基本操作如下:

	ListState.add(value: T)
	
	ListState.addAll(values: java.util.List[T])
	
	ListState.get()返回Iterable[T]
	
	ListState.update(values: java.util.List[T])

主要代码如下:


class MyMapperFunction1 extends  RichMapFunction[SensorReading3,String] {

  // 定义list类型的状态,可以看出状态是一个list类型。
  var listState: ListState[Int] =_


  override def open(parameters: Configuration): Unit = {
      listState =
        getRuntimeContext.getListState( new ListStateDescriptor[Int]("listState",classOf[Int]))

  }

  override def map(value: SensorReading3): String = {

    // 在当前的状态中 添加一个元素。
    listState.add(1)

    val list = new util.ArrayList[Int]()
    list.add(2)
    list.add(3)

    // 在当前的状态中添加一个 list集合。
    listState.addAll(list)


    // 更新集合。
    listState.update(list)

    // 获取某个元素
    listState.get()

     value.id
  }
}

MapState

拿mapfunction做举例,都是一样的,里面的代码。

case class SensorReading3 (id :String , timestamp: Long ,tempreture:Double)

MapState[K, V]保存 Key-Value 对。

	MapState.get(key: K)
	
	MapState.put(key: K, value: V)
	
	MapState.contains(key: K)
	
	MapState.remove(key: K)

主要代码如下:

class MyMapperFunction2 extends  RichMapFunction[SensorReading3,String] {

  // 定义list类型的状态,可以看出状态是一个list类型。
  var mapState: MapState[String, Double]  =_

  override def open(parameters: Configuration): Unit = {

    mapState=
       getRuntimeContext.getMapState( new MapStateDescriptor[String , Double] ("mapstate",classOf[String],classOf[Double]) )

  }


  override def map(value: SensorReading3): String = {

    //判断key中有没有这个key
    mapState.contains("sensor_1")

    //获取key为这个值的value
    mapState.get("sensor_1")

    //往map中放入一个(k,v)
    mapState.put("sensor_1", 1.3)

    value.id
  }
}

ReducingState

拿mapfunction做举例,都是一样的,里面的代码。

在这里插入图片描述

点进去,查看类型,参数是两个类型的对象,和一个类型。string类型的对象,直接传一个字符串就是一个对象,因为是普通的数据类型。后面就是正常的代码构成的类,那么就得进行new,先看看是不是Scala类型的,如果不是的话,那么就new,也就是通常会有public类似这种。

在这里插入图片描述

在这里插入图片描述

case class SensorReading3 (id :String , timestamp: Long ,tempreture:Double)

class MyMapperFunction3  extends  RichMapFunction[SensorReading3,String] {


  var reduceState: ReducingState[SensorReading3] = _


  override def open(parameters: Configuration): Unit = {

    reduceState =
     getRuntimeContext.getReducingState(
       new ReducingStateDescriptor[SensorReading3]("ReduceState",new Myreducefuction,classOf[SensorReading3])
     )

  }

  override def map(value: SensorReading3): String = {

    // 之前聚合后的值,也就是历史数据。
    reduceState.get()

    // 当前的值 和 之前的 状态值 进行 聚合,也就是本次聚合后的结果。
    reduceState.add(value)
    value.id
  }


}


class  Myreducefuction extends ReduceFunction[SensorReading3] {
  override def reduce(value1: SensorReading3, value2: SensorReading3): SensorReading3 = {

  // 里面怎么实现,看具体的需求。
  SensorReading3(value2.id,value2.timestamp,value2.tempreture.min(value1.tempreture))
  
  }
}

键控状态在代码中的使用

示例1

没有初值,状态中的初值是0。

package com.atguigu.apitest

import org.apache.flink.api.common.functions.{RichFlatMapFunction, RichMapFunction}

import org.apache.flink.api.common.state._

import org.apache.flink.configuration.Configuration

import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector

case class SensorReading4 (id :String , timestamp: Long ,tempreture:Double)


object StateTest {

  def main(args: Array[String]): Unit = {

    // 定义流式处理环境。
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    val inputStream = env.socketTextStream("hadoop102", 7777)

    val dataStream = inputStream
      .map(data =>{
        val arr = data.split(",")
        SensorReading4(arr(0), arr(1).toLong, arr(2).toDouble)
      } )


   // 需求: 对于温度传感器的跳变,超过10度,报警。

   val  alertStream = dataStream
        .keyBy(_.id)
     .flatMap(new myFlatMapFunction(10))

    alertStream.print()

   env.execute("state test")

  }


}


class myFlatMapFunction(gap:Double) extends  RichFlatMapFunction[SensorReading4,(String,Double,Double)]{


  var lastState:ValueState[Double] =_

  override def open(parameters: Configuration): Unit = {

    lastState  =
    getRuntimeContext.getState(new ValueStateDescriptor[Double]("map-state",classOf[Double]))

  }

  override def flatMap(value: SensorReading4, out: Collector[(String,Double,Double)]): Unit = {


    val tempreture1 = lastState.value()
    val tempreture2 = value.tempreture

    val diff = (tempreture1 - tempreture2).abs

    if(  diff > gap )
        //输出
      out.collect(value.id,tempreture1,tempreture2)

    // 每次都要更新才可以
    //下一次比较的时候,这个作为last。数据不断的来,每一次都会成为历史,成为上一次的状态。
    lastState.update(value.tempreture)
  }

}

ProcessFunction API

最底层的API
Process Function 是加强版的richfunction, open()、close()和 getRuntimeContext()都有。
Process Function 特有的功能:

1、定时器( onTimer): 是一个回调函数。当之前注册的定时器触发时调用。
2、侧输出流: 迟到数据放到侧输出流中。
3、processElement:流中的每一个元素都会调用这个方法, 调用结果将会放在 Collector  数据类型中输出。

ProcessFunction代码示例

Flink 提供了 8Process Function:

ProcessFunctionKeyedProcessFunctionCoProcessFunctionProcessJoinFunctionBroadcastProcessFunctionKeyedBroadcastProcessFunctionProcessWindowFunctionProcessAllWindowFunction

OnTimer()定时器的示例

10s间隔如果温度连续上升,则报警,如果10s内有下降,则重新作为判断起点。

package com.atguigu.apitest

import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector
case class SensorReading5 (id :String , timestamp: Long ,tempreture:Double)


object ProcessFunctionTest {

  def main(args: Array[String]): Unit = {

    // 定义流式处理环境。
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    val inputStream = env.socketTextStream("hadoop102", 7777)

    val dataStream = inputStream
      .map(data =>{
        val arr = data.split(",")
        SensorReading5(arr(0), arr(1).toLong, arr(2).toDouble)
      } )

  val resultdatastream =  dataStream
      .keyBy(_.id)
      .process(new TimeIncreaseProcessFunction(10000L))

    resultdatastream.print()
    env.execute()
  }

}

// key是id,所以是 string类型。
class TimeIncreaseProcessFunction(interval:Long) extends KeyedProcessFunction[String,SensorReading5,String]{

  //定义状态: 1、保存上一个温度值进行比较  2、保存定时器的时间戳用于删除定时器。

  var lastState:ValueState[Double] =_
  var timerTsState:ValueState[Long] =_

  override def open(parameters: Configuration): Unit = {

    lastState  =
      getRuntimeContext.getState(new ValueStateDescriptor[Double]("map-state",classOf[Double]))

    timerTsState  =
      getRuntimeContext.getState(new ValueStateDescriptor[Long]("timer-state",classOf[Long]))

  }


  override def processElement(value: SensorReading5, ctx: KeyedProcessFunction[String, SensorReading5, String]#Context, out: Collector[String]): Unit ={

    val lastTempreture = lastState.value()
    val timerTs = timerTsState.value()

    val curTempreture = value.tempreture

    if( curTempreture - lastTempreture>0 && timerTs == 0 ){

      // 如果温度上升,且没有定时器,那么注册当前数据时间戳10s之后的定时器
      // -- 连续上升的,啥也不用做,不在判断语句内。
      val  ts  =  ctx.timerService().currentProcessingTime() +interval
      ctx.timerService().registerProcessingTimeTimer(ts)
      timerTsState.update(ts)
    }else if(curTempreture - lastTempreture<0){
       // 如果温度下降,删除定时器
      //连续下降的话,没有定时器可删除。那么久就啥也不做。
      if(timerTs >0){
        ctx.timerService().deleteProcessingTimeTimer(timerTs)
        timerTsState.clear()
      }

    }

    // 状态时刻更新,时刻都成为下一个的上一个。
    lastState.update(value.tempreture)
  }


  override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[String, SensorReading5, String]#OnTimerContext, out: Collector[String]): Unit = {


    out.collect(""+ctx.getCurrentKey+"温度连续"+interval+"s 连续上升")
    // 定时器触发之后,也要清空状态。
    timerTsState.clear()
  }

}

侧输出流

外边: getSideOutput
方法里: ctx.output

代码演示

温度大于30的放在主流,小于等于的放在侧输出流。

package com.atguigu.apitest


import org.apache.flink.streaming.api.functions.{ ProcessFunction}
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector

case class SensorReading6 (id :String , timestamp: Long ,tempreture:Double)


object sideOutputsideTest {

  def main(args: Array[String]): Unit = {

    // 定义流式处理环境。
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    val inputStream = env.socketTextStream("hadoop102", 7777)

    val dataStream = inputStream
      .map(data =>{
        val arr = data.split(",")
        SensorReading6(arr(0), arr(1).toLong, arr(2).toDouble)
      } )


    val  hightemp = dataStream
       .process(new SplittempProcessor(30))

        //主流,主流直接打印
        hightemp.print("high")
    
       //侧输出流
        hightemp.getSideOutput(new OutputTag[SensorReading6]("low")).print("low")



       env.execute()
    

  }
}

// 实现自定义的processfunction,然后实现分流。
// 这里定义的是主流的 输出类型。
class SplittempProcessor(gap:Double) extends  ProcessFunction[SensorReading6,SensorReading6]{


  override def processElement(value: SensorReading6, ctx: ProcessFunction[SensorReading6, SensorReading6]#Context, out: Collector[SensorReading6]): Unit = {

     if(value.tempreture >30){

        //如果温度大于30度,那么输出到主流。
       out.collect(value)  // out是主流的输出方式。

     }
     else{
        // 如果不超多30度,那么输出到侧输出流。
       //侧输出流的话,用到上下文 ctx。
       ctx.output(new OutputTag[SensorReading6]("low"),value)
     }
  }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值