流式计算分为无状态和有状态两种情况。无状态的计算观察每个独立事件,并根据最后一个事件输出结果。例如,流处理应用程序从传感器接收温度读数,并在温度超过90度时发出警告。有状态的计算则会基于多个事件输出结果。以下是一些例子。
- 所有类型的窗口。例如,计算过去一小时的平均温度,就是有状态的计算。
- 所有用于复杂事件处理的状态机。例如,若在一分钟内收到两个相差20度以上的温度读数,则发出警告,这是有状态的计算。
- 流与流之间的所有关联操作,以及流与静态表或动态表之间的关联操作,都是有状态的计算。
下图展示了无状态流处理和有状态流处理的主要区别。无状态流处理分别接收每条数据记录(图中的黑条),然后根据最新输入的数据生成输出数据(白条)。有状态流处理会维护状态(根据每条输入记录进行更新),并基于最新输入的记录和当前的状态值生成输出记录(灰条)。
图 无状态和有状态的流处理
上图中输入数据由黑条表示。无状态流处理每次只转换一条输入记录,并且仅根据最新的输入记录输出结果(白条)。有状态 流处理维护所有已处理记录的状态值,并根据每条新输入的记录更新状态,因此输出记录(灰条)反映的是综合考虑多个事件之后的结果。
尽管无状态的计算很重要,但是流处理对有状态的计算更感兴趣。事实上,正确地实现有状态的计算比实现无状态的计算难得多。旧的流处理系统并不支持有状态的计算,而新一代的流处理系统则将状态及其正确性视为重中之重。
9.1 有状态的算子和应用程序
Flink内置的很多算子,数据源source,数据存储sink都是有状态的,流中的数据都是buffer records,会保存一定的元素或者元数据。例如: ProcessWindowFunction会缓存输入流的数据,ProcessFunction会保存设置的定时器信息等等。
在Flink中,状态始终与特定算子相关联。总的来说,有两种类型的状态:
- 算子状态(operator state)
- 键控状态(keyed state)
9.3.1 算子状态(operator state)
算子状态的作用范围限定为算子任务。这意味着由同一并行任务所处理的所有数据都可以访问到相同的状态,状态对于同一任务而言是共享的。算子状态不能由相同或不同算子的另一个任务访问。
图 具有算子状态的任务
Flink为算子状态提供三种基本数据结构:
- 列表状态(List state)
将状态表示为一组数据的列表。
- 联合列表状态(Union list state)
也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复。
- 广播状态(Broadcast state)
如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。
9.3.2 键控状态(keyed state)
键控状态是根据输入数据流中定义的键(key)来维护和访问的。Flink为每个键值维护一个状态实例,并将具有相同键的所有数据,都分区到同一个算子任务中,这个任务会维护和处理这个key对应的状态。当任务处理一条数据时,它会自动将状态的访问范围限定为当前数据的key。因此,具有相同key的所有数据都会访问相同的状态。Keyed State很类似于一个分布式的key-value map数据结构,只能用于KeyedStream(keyBy算子处理之后)。
图 具有键控状态的任务
Flink的Keyed State支持以下数据类型:
- ValueState[T]保存单个的值,值的类型为T。
- get操作: ValueState.value()
- set操作: ValueState.update(value: T)
- 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])
- MapState[K, V]保存Key-Value对。
- MapState.get(key: K)
- MapState.put(key: K, value: V)
- MapState.contains(key: K)
- MapState.remove(key: K)
- ReducingState[T]
- AggregatingState[I, O]
State.clear()是清空操作。
sensor_1,1547718199,35.8
sensor_6,1547718201,15.4
sensor_7,1547718202,6.7
sensor_10,1547718205,38.1
sensor_1,1547718129,29.8
sensor_1,1547718158,5.8
sensor_1,1547718140,40.8
sensor_1,1547718111,11.8
package com.state
import java.util
import com.sinktest.MyReducer
import org.apache.flink.api.common.functions.{ReduceFunction, RichFlatMapFunction, RichMapFunction}
import org.apache.flink.api.common.state._
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector
//定义样例类,温度传感器
case class SensorReading6(id:String,timestamp:Long,temperature:Double)
object StateTest {
def main(args: Array[String]): Unit = {
val env=StreamExecutionEnvironment.getExecutionEnvironment
val inputStream=env.socketTextStream("localhost",7777)
//1.先转换成样例类类型
val dataStream= inputStream.map(data=>{
val arr = data.split(",")
SensorReading6(arr(0),arr(1).toLong,arr(2).toDouble)
})
//需求:对于温度传感器温度值跳变超过10度报警
val alertStream = dataStream
.keyBy(_.id)
//.flatMap( new TempChangeAlert(10.0))
.flatMapWithState[(String,Double,Double),Double]({//第一个参数表示返回的是三元组
case(data:SensorReading6,None)=>(List.empty,Some(data.temperature))
case(data:SensorReading6,lastTemp:Some[Double])=>{
//跟最新的温度值求差值做比较
val diff=(data.temperature-lastTemp.get).abs
if(diff>10.0){
(List((data.id,lastTemp.get,data.temperature)),Some(data.temperature))
}else{
(List.empty,Some(data.temperature))
}
}
})
alertStream.print()
env.execute("state test")
}
}
//实现自定义RichFlatmapFunction
class TempChangeAlert(threhold:Double ) extends RichFlatMapFunction[SensorReading6,(String,Double,Double)]{
// 定义状态保存上一次的温度值
lazy val lastTempState:ValueState[Double]=getRuntimeContext.getState(new ValueStateDescriptor[Double]("last-temp",classOf[Double]))
override def flatMap(in: SensorReading6, collector: Collector[(String, Double, Double)]): Unit = {
//获取上一次的温度值
val lastTemp=lastTempState.value()
//跟最新的温度值求差值做比较
val diff=(in.temperature-lastTemp).abs
if(diff>threhold){
//输出上一次的温度 和 当前的温度
collector.collect((in.id,lastTemp,in.temperature))
}
lastTempState.update(in.temperature)
}
}
//keyed state测试:必须定义在RichFunction中,因为需要运行时上下文
class MyRichMapper1 extends RichMapFunction[SensorReading6,String]{
var valueState:ValueState[Double] = _
lazy val listState:ListState[Int] = getRuntimeContext.getListState(new ListStateDescriptor[Int]("liststate",classOf[Int]) )
lazy val mapState:MapState[String,Double]=getRuntimeContext.getMapState(new MapStateDescriptor[String,Double]("mapstate",classOf[String],classOf[Double]))
lazy val reduceState:ReducingState[SensorReading6]=getRuntimeContext.getReducingState(new ReducingStateDescriptor[SensorReading6]("reducingstate",new MyReducer1,classOf[SensorReading6]))
override def open(parameters: Configuration): Unit = {
valueState=getRuntimeContext.getState(new ValueStateDescriptor[Double]("valuestate",classOf[Double]))
}
override def map(in: SensorReading6): String = {
//状态的读写
val myV=valueState.value()
valueState.update(in.temperature)
listState.add(1)
val list= new util.ArrayList[Int]()
list.add(2)
list.add(3)
listState.addAll(list)
listState.update(list)//直接替换
mapState.get("sensor_1")
mapState.put("sensor_1",1.0)
reduceState.get()
reduceState.add(in)
in.id
}
}
class MyReducer1 extends ReduceFunction[SensorReading6]{
override def reduce(t1: SensorReading6, t2: SensorReading6): SensorReading6 = {
SensorReading6(t1.id,t2.timestamp,t1.temperature.min(t2.temperature))
}
}