流式计算分为无状态和有状态两种情况。
无状态的计算观察每个独立事件,并根据最后一个事件输出结果。
无状态流处理过程如下:
无状态流每次只转换处理一条输入记录,并且只根据最新的输入记录输出结果。
下列示例均为有状态流处理:
有状态的计算则会基于多个事件输出结果。有状态流处理过程如下:
有状态流需要维护所有已处理记录的状态值,并且根据每条输入的记录更新状态,因此输出记录反映的是综合多个事件之后的结果。
下列示例均为有状态流处理:
Flink内置的很多算子,数据源source,数据存储 sink都是有状态的,流中的数 据都是buffer records,会保存一定的元素或者元数据。例如: ProcessWindowFunction会缓存输入流的数据,ProcessFunction 会保存设置的定时器信息等等。在 Flink 中,状态始终与特定算子相关联。
总的来说,可以分为下列两大类:
算子状态的作用范围限定为算子任务。这意味着由同一并行任务所处理的所有 数据都可以访问到相同的状态,状态对于同一任务而言是共享的。算子状态不能由 相同或不同算子的另一个任务访问。
如上图所示,算子的状态对于同一任务中的所有数据都是共享的,但是对于在不同并行度中的同一任务,只能访问各自的状态。(跨并行度,资源在不同的slot中,对各自是不可见,有独立的内存空间)
Flink为算子状态提供三种基本数据结构:
键控状态是根据输入数据流中定义的键(key)来维护和访问的。Flink 为每个键值维护 一个状态实例,并将具有相同键的所有数据,都分区到同一个算子任务中,这个任务会维护 和处理这个 key 对应的状态。当任务处理一条数据时,它会自动将状态的访问范围限定为当 前数据的 key。因此,具有相同 key 的所有数据都会访问相同的状态。Keyed State 很类似于 一个分布式的 key-value map 数据结构,只能用于 KeyedStream(keyBy 算子处理之后)。
如上图所示:
Flink 的Keyed State支持以下数据类型:
保存单个的值,值的类型为T。
//定义一个ValueState
lazy val lastTempState:ValueState[Double] = getRuntimeContext.getState(new ValueStateDescriptor[Double]("lastTempState",classOf[Double]))
//获取值
lasteTempState.value()
//设置值
lasteTempState.update(T value)
//清空状态
lasteTempState.clear()
保存一个列表,列表里的元素的数据类型为 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
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()
每往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()
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 The type of the values that are aggregated (input values)
* @param The type of the accumulator (intermediate aggregate state).
* @param The type of the aggregated result
*/
@PublicEvolving
public interface AggregateFunction 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)
}
}