Flink会将状态以二进制的形式全部存储起来
算子状态(operator state 一个任务一个状态)
“所有并行的子任务对应着一个状态,也就是到子任务来的所有数据共享一个状态”
Flink 为算子状态提供三种基本数据结构:
列表状态(List state) 将状态表示为一组数据的列表。 (会根据并行度的调整直接把之前的状态重新分组重新分配)
联合列表状态(Union list state)也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保 存点(savepoint)启动应用程序时如何恢复。 (会把之前的状态广播到每个算子中)
广播状态(Broadcast state)如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态
键控状态(keyed state 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()是清空操作。
状态后端
管理整个flink程序的状态的存储(插入,更新)、访问、维护,除了管理本地外,还管理checkpoint,将其写入远程管理
提供了三个状态后端类型
内存级的状态后端,一般不用再生产当中,只是用在开发和测试之中。实质生产环境中用的是后面两种
rockdb相当于一个本地数据库,将数据序列化后,存到本地中
env.getCheckpointConfig.setMaxConcurrentCheckpoints(1) //间隔多久做一次快照保存
env.setStateBackend( new RocksDBStateBackend("") )
状态编程
检测两次温度范围如果太大则报警
object ProcessFunctionTestTwo {
case class SensorReading(id: String, timestamp: Long, temperature: Double)
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
val stream = env.socketTextStream("localhost", 7777)
val dataStream = stream.map(data => {
val dataArray = data.split(",")
SensorReading(dataArray(0).trim, dataArray(1).trim.toLong, dataArray(2).trim.toDouble)
})
// 处理乱序问题
.assignTimestampsAndWatermarks( new BoundedOutOfOrdernessTimestampExtractor[SensorReading]( Time.seconds(1) ) {
override def extractTimestamp(element: SensorReading): Long = element.timestamp * 1000
} )
val processedStream2 = dataStream.keyBy(_.id)
.process( new TempChangeAlert2(10.0) ) // 能用状态编程
// .flatMap( new TempChangeAlert(10.0) ) // 也能用状态编程
// 这个也能进行状态编程
val processedStream3 = dataStream.keyBy(_.id)
.flatMapWithState[(String, Double, Double), Double]{
// 如果没有状态的话,也就是没有数据来过,那么就将当前数据温度值存入状态
case ( input: SensorReading, None ) => ( List.empty, Some(input.temperature) )
// 如果有状态,就应该与上次的温度值比较差值,如果大于阈值就输出报警
case ( input: SensorReading, lastTemp: Some[Double] ) =>
val diff = ( input.temperature - lastTemp.get ).abs
if( diff > 10.0 ){
// list是一个三元组, some里面是直接更新温度
( List((input.id, lastTemp.get, input.temperature)), Some(input.temperature) )
} else
( List.empty, Some(input.temperature) )
}
dataStream.print("input data")
env.execute("process function test")
}
}
/*
传了一个参数threshold
*/
class TempChangeAlert2(threshold: Double) extends KeyedProcessFunction[String, SensorReading, (String, Double, Double)]{
// 1、定义一个状态变量,保存上次的温度值
lazy val lastTempState: ValueState[Double] = getRuntimeContext.getState( new ValueStateDescriptor[Double]("lastTemp", classOf[Double]) )
override def processElement(value: SensorReading, ctx: KeyedProcessFunction[String, SensorReading, (String, Double, Double)]#Context, out: Collector[(String, Double, Double)]): Unit = {
// 2、获取上次的温度值
val lastTemp = lastTempState.value()
// 3、用当前的温度值和上次的求差,如果大于阈值,输出报警信息
val diff = (value.temperature - lastTemp).abs // 做一个绝对值
if(diff > threshold){
out.collect( (value.id, lastTemp, value.temperature) )
}
lastTempState.update(value.temperature) // 更新最新一次的状态
}
}
class TempChangeAlert(threshold: Double) extends RichFlatMapFunction[SensorReading, (String, Double, Double)]{
// 先定义
private var lastTempState: ValueState[Double] = _
override def open(parameters: Configuration): Unit = {
// 初始化的时候声明state变量 (open的时候能从上下文拿到东西)
lastTempState = getRuntimeContext.getState(new ValueStateDescriptor[Double]("lastTemp", classOf[Double]))
}
override def flatMap(value: SensorReading, out: Collector[(String, Double, Double)]): Unit = {
// 获取上次的温度值
val lastTemp = lastTempState.value()
// 用当前的温度值和上次的求差,如果大于阈值,输出报警信息
val diff = (value.temperature - lastTemp).abs // 做一个绝对值
if(diff > threshold){
out.collect( (value.id, lastTemp, value.temperature) )
}
lastTempState.update(value.temperature) // 更新最新一次的状态
}
}
容错机制
如何用checkpoin去保存状态
怎样从检查点去恢复状态
一致性检查点:
在流式处理中,应该按照同一时间点,不是物理意义的同一时刻,而是让数据流动,流动到让所有数据处理完某一个数据的时候。这个时候把他们的状态拍下了,就能保证已经处理完了,虽然没有存。但是能保证已经处理完了。
offset是偏移量,当偏移量5保存下了后,也就是说把5这个数已经保存下来了。而且已经处理完了。
2+4=6 1+3+5=9 ,当前的状态是5这个数从头到尾都处理完了,要保存的是所有的数据把5以前的数据都处理完了之后,也就是5 6 9都保存在状态后端,存在checkpoint
Flink会定期保存checkpoint ,如果异常了,如何恢复。
从检查点恢复状态
注意: 这是说的是内部保证精准一次性,如果输出到外部则就不能保证了。
先暂停从源里面读取数据,然后进行状态保存,保存完后在通知JobManager
// 开启检查点 (在去配一个重启策略)
env.enableCheckpointing(60000)
// 不同的状态一致性级别的设置
env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.AT_LEAST_ONCE)
// 设置超时时间 操作这个时间就不保存了
env.getCheckpointConfig.setCheckpointTimeout(100000)
// 检查点出现异常的时候,就将Checkpoint关闭掉=false
env.getCheckpointConfig.setFailOnCheckpointingErrors(false)
// 间隔多久做一次快照保存 (同时进行几个Checkpoint) 默认是1
//env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
// 两次Checkpoint之间的最小时间间隔
env.getCheckpointConfig.setMinPauseBetweenCheckpoints(100)
// 开启一个Checkpoint外部的持久化(如果job failed是会被自动清理掉的)
env.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION)
// 重启策略,
env.setRestartStrategy(RestartStrategies.failureRateRestart(3, org.apache.flink.api.common.time.Time.seconds(300), org.apache.flink.api.common.time.Time.seconds(10)))
端到端的精准一次性
要么全部成功了,要么全部不做(不成功)