Flink中的状态管理
1、Flink中的状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3E3FtJlw-1601270265116)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200902181631638.png)]
- 由一个任务维护,并且用来计算某个结果的所有数据,都属于这个任务的状态
- 可以认为状态就是一个本地变量,可以被任务的业务逻辑访问
- Flink 会进行状态管理,包括状态一致性、故障处理以及高效存储和访问,以便开发人员可以专注于应用程序的逻辑
2、状态分类
- 算子状态(不常用):算子状态的作用范围限定为算子任务,同一并行任务所处理的所有数据都可以访问到相同的状态,且状态对于同一子任务共享,算子状态不能有相同或不同算子的另一个子任务访问
- 键控状态:根据输入数据流中定义的键(key)来维护和访问,只能访问键相同的状态
3、算子状态的数据结构
- 列表状态:(list state) 将状态表示为一组数据的列表
- 联合列表状态:(union list state)•也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复
- 广播状态:(broadcast state)•如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。
4、键控状态数据结构
-
值状态:(value state) 将状态表示为单个的值
- get操作: ValueState.value()
- set操作: ValueState.update(value: T)
-
列表状态:(list state)将状态保存为一组数据的列表
- ListState.add(value: T)
- ListState.addAll(values: java.util.List[T])
- ListState.get()返回Iterable[T]
- ListState.update(values: java.util.List[T])
-
映射状态:(map state)将状态表示为一组key-value对
- MapState.get(key: K)
- MapState.put(key: K, value: V)
- MapState.contains(key: K)
- MapState.remove(key: K)
-
聚合状态:(reducing state & aggregating state)将状态表示为一个用于聚合操作的列表
-
State.clear()是清空操作。
5、键控状态的声明以及使用
方式一:通过懒加载的方式声明
class MyTemProcess(interval: Int) extends KeyedProcessFunction[Tuple,SensorReading,String]{
//********************************** 状态的声明 *********************************
lazy val lastTem : ValueState[Double] = getRuntimeContext.getState(new ValueStateDescriptor[Double]("last_tem",classOf[Double]))
lazy val curTimerTsState : ValueState[Long] = getRuntimeContext.getState(new ValueStateDescriptor[Long]("current_time_ts",classOf[Long]))
override def processElement(value: SensorReading, ctx: KeyedProcessFunction[Tuple, SensorReading, String]#Context, out: Collector[String]): Unit = {
//******************************* 获取及更新状态 *************************************
val lastTemp: Double = lastTem.value()
val curTemp: Long = curTimerTsState.value()
lastTem.update(value.temperature)
//如果温度上升,且没有上升则注册一个定时器
if(value.temperature >lastTemp && curTemp == 0){
ctx.timerService().registerProcessingTimeTimer(ctx.timerService().currentProcessingTime() + interval)
curTimerTsState.update(ctx.timerService().currentProcessingTime() + interval)
}else if (value.temperature < lastTemp){
ctx.timerService().deleteEventTimeTimer(curTemp)
curTimerTsState.clear()
}
}
//触发报警
override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Tuple, SensorReading, String]#OnTimerContext, out: Collector[String]): Unit = {
out.collect(s"传感器${ctx.getCurrentKey}的温度已经连续${interval/1000}秒上升了")
curTimerTsState.clear()
}
}
方式二:在richFunction的生命周期内声明
class MyKeyedProcessFunction extends KeyedProcessFunction[Tuple,SensorReading,Int]{
//****************************** 定义状态 ***************************
var myState : ListState[Int] = _
override def open(parameters: Configuration): Unit = {
// **************************** 初始化状态 ****************************
myState = getRuntimeContext.getListState(new ListStateDescriptor[Int]("my-state",classOf[Int]))
}
override def processElement(value: SensorReading,
ctx: KeyedProcessFunction[Tuple, SensorReading, Int]#Context,
out: Collector[Int]): Unit = {
myState.get()
myState.update(new util.ArrayList[Int]())
myState.add(10)
ctx.output(new OutputTag[Int]("side"),10)
ctx.getCurrentKey
ctx.timerService().currentWatermark()
ctx.timerService().registerEventTimeTimer(ctx.timerService().currentWatermark()+10)
}
//计时器到时执行
override def onTimer(timestamp: Long,
ctx: KeyedProcessFunction[Tuple, SensorReading, Int]#OnTimerContext,
out: Collector[Int]): Unit = {
println("timer occur")
}
}
6、状态后端
- 每传入一条数据,有状态的算子任务都会读取和更新状态
- 由于有效的状态访问对于处理数据的低延迟至关重要,因此每个并行任务都会在本地维护其状态,以确保快速的状态访问
- 状态的存储、访问以及维护,由一个可插入的组件决定,这个组件就叫做状态后端(state backend)
- 状态后端主要负责两件事:本地的状态管理,以及将检查点(checkpoint)状态写入远程存储
7、状态后端的类型
-
MemoryStateBackend:内存级的状态后端,会将键控状态作为内存中的对象进行管理,将它们存储在 TaskManager 的 JVM 堆上,而将 checkpoint 存储在 JobManager 的内存中
- 特点:快速,低延迟,不稳定
-
FsStateBackend:将 checkpoint 存到远程的持久化文件系统(FileSystem)上,而对于本地状态,跟 MemoryStateBackend 一样,也会存在 TaskManager 的 JVM 堆上
- 特点:同时拥有内存级的本地访问速度,和更好的容错保证
-
RocksDBStateBackend(生产环境中使用):将所有状态序列化后,存入本地的 RocksDB 中存储。
- 特点:适合数据量大的场景,可以设置同步的机制,全量or增量
Flink中的容错机制
1、一致性检查点checkPoint
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xCdpDw9Y-1601270265118)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200902184943970.png)]
- Flink 故障恢复机制的核心,就是应用状态的一致性检查点
- 有状态流应用的一致检查点,其实就是所有任务的状态,在某个时间点的一拷贝(一份快照);这个时间点,应该是所有任务都恰好处理完一个相同的输入数据的时候
2、检查点恢复状态
- 在执行流应用程序期间,Flink 会定期保存状态的一致检查点
- 如果发生故障, Flink 将会使用最近的检查点来一致恢复应用程序的状态,并重新启动处理流程
3、基于Chandy-Lamport算法的分布式快照
-
检查点分界线(Checkpoint Barrier)
- Flink 的检查点算法用到了一种称为分界线(barrier)的特殊数据形式,用来把一条流上数据按照不同的检查点分开
- 分界线之前到来的数据导致的状态更改,都会被包含在当前分界线所属的检查点中;而基于分界线之后的数据导致的所有更改,就会被包含在之后的检查点中
4、保存点(Savepoints)
- Flink 还提供了可以自定义的镜像保存功能,就是保存点(savepoints)
- 原则上,创建保存点使用的算法与检查点完全相同,因此保存点可以认为就是具有一些额外元数据的检查点
- Flink不会自动创建保存点,因此用户(或者外部调度程序)必须明确地触发创建操作
- 保存点是一个强大的功能。除了故障恢复外,保存点可以用于:有计划的手动备份,更新应用程序,版本迁移,暂停和重启应用,等等
5、CheckPoint在代码中的使用
object CheckPointSet {
def main(args: Array[String]): Unit = {
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)//默认是exactly-once
env.getCheckpointConfig.setCheckpointInterval(10000L)//设置source端,checkPoint的barrier的植入间隔
env.getCheckpointConfig.setCheckpointTimeout(10000L) //设置checkpoint的超时时间,超出该时间,则checkpoint失败
env.getCheckpointConfig.setMaxConcurrentCheckpoints(2) //设置允许同时进行的checkPoint数量
env.getCheckpointConfig.setMinPauseBetweenCheckpoints(500L) //设置截止端checkpoint之间的最小间隔
env.getCheckpointConfig.setPreferCheckpointForRecovery(true) //优先从checkpoint进行数据恢复,一般选择默认false
}
}
6、重启策略设置
// 重启策略,一般选择第一种
env.setRestartStrategy(RestartStrategies.fixedDelayRestart(5, 10000L))
env.setRestartStrategy(RestartStrategies.noRestart())
Flink中的状态一致性
1、什么是状态一致性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tsc9yPf9-1601270265119)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200902200315164.png)]
- 有状态的流处理,内部每个算子任务都可以有自己的状态
- 对于流处理器内部来说,所谓的状态一致性,其实就是我们所说的计算结果要保证准确。
- 一条数据不应该丢失,也不应该重复计算
- 在遇到故障时可以恢复状态,恢复以后的重新计算,结果应该也是完全正确的。
2、状态一致性分类
- AT-MOST-ONCE(最多一次)
- AT-LEAST-ONCE(至少一次)
- EXACTLY-ONCE(精确一次)
3、Flink中的状态一致性保证
- Flink中通过CheckPoint保证状态一致性
4、端到端的状态一致性
-
除了需要在Flink中实现状态一致性,也许要在source源和sink输出端实现状态一致性
-
整个端到端的一致性级别取决于所有组件中一致性最差的组件
-
source端:若可重设数据的读取位置,则可以在checkpoint恢复时,从新设置读取位置,实现精确一次;
若是不可重设数据的读取位置,则只能保证最多一次的一致性状态
-
sink端:从故障恢复时,数据不重复写入外部系统,实现方式有两种
- 写入幂等性框架:如redis、hbase时,则可保证精准一次性
- 写入支持事务的框架:通过两次提交手段,将写入操作与checkpoint组成事务,实现精准一次性
5、事务的实现方式
-
预写日志(write-Ahead-Log,WAL)
-
两阶段提交
- 对于每个 checkpoint,sink 任务会启动一个事务,并将接下来所有接收的数据添加到事务里
- 然后将这些数据写入外部 sink 系统,但不提交它们 —— 这时只是“预提交”
- 当它收到 checkpoint 完成的通知时,它才正式提交事务,实现结果的真正写入
- 这种方式真正实现了 exactly-once,它需要一个提供事务支持的外部 sink 系统。Flink 提供了 TwoPhaseCommitSinkFunction 接口。
6、两阶段提交对外部sink系统的要求
- 外部 sink 系统必须提供事务支持,或者 sink 任务必须能够模拟外部系统上的事务
- 在 checkpoint 的间隔期间里,必须能够开启一个事务并接受数据写入
- 在收到 checkpoint 完成的通知之前,事务必须是“等待提交”的状态。在故障恢复的情况下,这可能需要一些时间。如果这个时候sink系统关闭事务(例如超时了),那么未提交的数据就会丢失
- sink 任务必须能够在进程失败后恢复事务
- 提交事务必须是幂等操作
7、不同source和sink的一致性保证
| source sink | 不可重置 | 可重置 |
|---|---|---|
| 任意(Any) | At-most-once | At-least-once |
| 幂等 | At-most-once | Exactly-once (故障恢复时会出现暂时不一致) |
| 预写日志(WAL) | At-most-once | At-least-once |
| At-most-once | At-least-once |
| 幂等 | At-most-once | Exactly-once (故障恢复时会出现暂时不一致) |
| 预写日志(WAL) | At-most-once | At-least-once |
| 两阶段提交(2PC) | At-most-once | Exactly-once |