FLink学习笔记:09-Flink 的状态编程应用

概述

流式计算分为无状态有状态两种情况。

无状态流计算

无状态的计算观察每个独立事件,并根据最后一个事件输出结果。
无状态流处理过程如下:
无状态流

无状态流每次只转换处理一条输入记录,并且只根据最新的输入记录输出结果。
下列示例均为有状态流处理:

  • 流处理应用程序从传感器接收温度读数,并在温度超过90度时发出警告。
  • 流处理应用程序从数据源接收到一条记录时,对数据进行文本大小写转换操作
  • 流处理应用程序从数据源接收到一条记录时,对数据进行数据类型转换操作等ETL处理。

有状态流计算

有状态的计算则会基于多个事件输出结果。有状态流处理过程如下:

有状态流处理

有状态流需要维护所有已处理记录的状态值,并且根据每条输入的记录更新状态,因此输出记录反映的是综合多个事件之后的结果。

下列示例均为有状态流处理:

  • 所有类型的窗口。例如,计算过去一小时的平均温度,就是有状态的计算。
  • 所有用于复杂事件处理的状态机。例如,若在一分钟内收到两个相差 20 度以上的温度读数,则发出警告,这是有状态的计算。
  • 流与流之间的所有关联操作,以及流与静态表或动态表之间的关联操作, 都是有状态的计算。

状态分类:

Flink内置的很多算子,数据源source,数据存储 sink都是有状态的,流中的数 据都是buffer records,会保存一定的元素或者元数据。例如: ProcessWindowFunction会缓存输入流的数据,ProcessFunction 会保存设置的定时器信息等等。在 Flink 中,状态始终与特定算子相关联。
总的来说,可以分为下列两大类:

  • 算子状态(operator state)
  • 键控状态(keyed state)

算子状态(Operator State)

算子状态的作用范围限定为算子任务。这意味着由同一并行任务所处理的所有 数据都可以访问到相同的状态,状态对于同一任务而言是共享的。算子状态不能由 相同或不同算子的另一个任务访问。

image

如上图所示,算子的状态对于同一任务中的所有数据都是共享的,但是对于在不同并行度中的同一任务,只能访问各自的状态。(跨并行度,资源在不同的slot中,对各自是不可见,有独立的内存空间)

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

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

键控状态(Keyed State)

键控状态是根据输入数据流中定义的键(key)来维护和访问的。Flink 为每个键值维护 一个状态实例,并将具有相同键的所有数据,都分区到同一个算子任务中,这个任务会维护 和处理这个 key 对应的状态。当任务处理一条数据时,它会自动将状态的访问范围限定为当 前数据的 key。因此,具有相同 key 的所有数据都会访问相同的状态。Keyed State 很类似于 一个分布式的 key-value map 数据结构,只能用于 KeyedStream(keyBy 算子处理之后)。
image

如上图所示:

  • 监控状态在同一算子任务中数据只能访问到自己Key对应的状态,而不能方位其他Key的状态
  • 不同任务中的数据只能访问各自的状态,无法跨任务共享状态

Flink 的Keyed State支持以下数据类型:

ValueState[T]

保存单个的值,值的类型为T。

//定义一个ValueState
lazy val lastTempState:ValueState[Double] = getRuntimeContext.getState(new ValueStateDescriptor[Double]("lastTempState",classOf[Double]))

//获取值
lasteTempState.value()

//设置值
lasteTempState.update(T value)

//清空状态
lasteTempState.clear()

ListState[T]

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

//定义一个ListState
lazy val listState:ListState[Double] = getRuntimeContext.getListState(new ListStateDescriptor[Double]("listState",classOf[Double]))

//获取数据
val data = listState.get()

//添加数据
listState.add(2.0)
val dataList = new util.ArrayList[Double]()
dataList.add(5.0)
dataList.add(5.2)
listState.addAll(dataList)

//清空状态
listState.clear()

//设置状态(覆盖原有list列表)
listState.update(dataList)

MapState[KU,VU]

保存一个键值对,基本操作如下:

//定义一个MapState
lazy val mapState:MapState[String,Double] = getRuntimeContext.getMapState(new MapStateDescriptor[String,Double]("mapState",classOf[String],classOf[Double]))
//设置值
mapState.put("00001",12.3)
val mapData = new util.HashMap[String,Double]()
mapData.put("00002",23.4)
mapData.put("00003",22.4)
mapState.putAll(mapData)
//判断是否包含某个键
mapState.contains("00003")

//删除键值对
mapState.remove("000003")
//判断是否为空
mapState.isEmpty
//获取所有的键
mapState.keys()
//获取所有的值
mapState.values()

//清空状态
mapState.clear()

ReducingState[T]

每往reduce状态中append一个数据,就会自动调用预先设定的reduce函数,基本操作如下所示:

class myReuceFunction extends ReduceFunction[Double] {
  override def reduce(value1: Double, value2: Double): Double = {
    value1+value2
  }
}

//定义一个ReducingState[T]
lazy val reduceState:ReducingState[Double] = getRuntimeContext.getReducingState(new ReducingStateDescriptor[Double]("reduceState",new myReuceFunction,classOf[Double]))
//添加值,会自动调用预先设定的reduce function
reduceState.add(23)
//获取值
reduceState.get()
//清空状态
reduceState.clear()

AggregatingState[I, O] 聚合状态


class myAggrateFunction extends AggregateFunction[Double,Double,Double]{

  override def createAccumulator(): Double = {
     0.0
  }

  override def add(value: Double, accumulator: Double): Double = {
    accumulator+value
  }

  override def getResult(accumulator: Double): Double = {
    accumulator
  }

  override def merge(a: Double, b: Double): Double = {
    a+b
  }
}

//创建AggregatingState[I, O]状态
lazy val aggregatingState = getRuntimeContext.getAggregatingState(new AggregatingStateDescriptor[Double,Double,Double]("aggrateState",new myAggrateFunction,classOf[Double] ))
//聚合运算
aggregatingState.add(23.0)
//获取结果
aggregatingState.get()
//清空状态
aggregatingState.clear()

AggregateFunction参数说明:IN 输入的数据类型,ACC 表示的是累加器的数据类型,OUT表述输出结果的数据类型

 * @param <IN> The type of the values that are aggregated (input values)
 * @param <ACC> The type of the accumulator (intermediate aggregate state).
 * @param <OUT> The type of the aggregated result
 */
@PublicEvolving
public interface AggregateFunction<IN, ACC, OUT> extends Function, Serializable {

TIPS: 以上所有的状态的声明和操作必须在RichFunction中,因为需要访问上下文环境。

状态运用编程实例

需求:对于温度传感器温度值跳变,超过10度,进行预警
代码:

package com.hjt.yxh.hw.apitest

import java.{lang, util}

import org.apache.flink.streaming.api.scala._
import org.apache.flink.api.common.functions.{RichFlatMapFunction, RichMapFunction}
import org.apache.flink.api.common.state.{ListState, ListStateDescriptor, ValueState, ValueStateDescriptor}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.util.Collector

object StateTest {

  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val inpuPath = "D:\\LearnWorkSpace\\FlinkDemo\\src\\main\\resources\\Data\\sensor.txt"

    val datastream = env.readTextFile(inpuPath)
      .filter(_.nonEmpty)
      .map(data=>{
        val array = data.split(",")
        SensorReading(array(0),array(1).toLong,array(2).toDouble)
      })

    //需求:对于温度传感器温度值跳变,超过10度,进行预警
    val alertStream = datastream
      .keyBy(data=>{
        data.id
      })
//      .flatMap(new TempChangeAlert(10.0))
        .flatMapWithState[(String,Double,Double),Double]({
      case (data:SensorReading,None) =>(List.empty,Some(data.temperature))
      case (data:SensorReading,lastTemp:Some[Double])=>{
        if ((data.temperature - lastTemp.get).abs >= 10.0){
          (List((data.id,lastTemp.get,data.temperature)),Some(data.temperature))
        }
        else {
          (List.empty,Some(data.temperature))
        }
      }
    })

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

class TempChangeAlert(threshold: Double) extends RichFlatMapFunction[SensorReading,(String,Double,Double)]{
  //定义状态保存上一次的温度值
  lazy val lastestTempState: ValueState[Double] = getRuntimeContext.getState(new ValueStateDescriptor[Double]("lastestTempState",classOf[Double]))
  lazy val firstFlagState:ValueState[Boolean]=getRuntimeContext.getState(new ValueStateDescriptor[Boolean]("firstFlagState",classOf[Boolean]))
  firstFlagState.update(true)

  override def flatMap(value: SensorReading, out: Collector[(String, Double, Double)]): Unit = {
    if (firstFlagState.value()) {
      //保存这一次的温度状态
      lastestTempState.update(value.temperature)
      return
    }
      //获取上一次的温度
      val lastTemp = lastestTempState.value()

      val diff = (value.temperature - lastTemp).abs
      if (diff > threshold)
        out.collect((value.id, lastTemp, value.temperature))

      //保存这一次的温度状态
      lastestTempState.update(value.temperature)
  }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值