flink状态管理
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 提供了 8 个 Process Function:
ProcessFunction
KeyedProcessFunction
CoProcessFunction
ProcessJoinFunction
BroadcastProcessFunction
KeyedBroadcastProcessFunction
ProcessWindowFunction
ProcessAllWindowFunction
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)
}
}
}