CheckPoint
- CheckPoint是Flink的容错核心机制。它可以定期将各个Operator处理的数据进行快照存储(Snapshot)。通过CheckPoint机制,可以对作业的状态和计算位置进行恢复。
barrier的产生
- Flink的checkpoint是由JobManager发起的,以一定的周期触发Source Task产生barrier。
- Source收到指令后生成barrier,Source Task中会进行两件事
1.首先生成携带CheckPoint ID的barrier,并将次barrier发送给下游。
2.然后处理本Task的状态保存,这样,在整个数据流中就有了barrier的传递。- barrier的传递
1.Source Task,会根据Job Master的指令周期性的在原始数据中插入barrier,并将barrier传递给下游operator。
2.对于非Source Task,在处理数据中,并不是周期性的触发checkpoint,而是当遇到barrier数据时,才会触发checkpoint机制。
每个需要CheckPoint的应用在启动时,Flink的JobManager为其创建一个CheckPointCoordinator(检查点协调器),CheckPointCoordinator(检查点协调器),负责此应用快照制作。
1.CheckpointCoordinator周期性的向该流应用的所有source算子发送barrier
2.当某个Source算子收到barrier,便会暂停处理数据,然后将自己的当前状态制作成快照,并将快照保存指定的存储中,然后CheckpointCoordinator报告自己制作情况,并向下游广播该barrier,恢复处理数据。
3.下游算子收到barrier后,会暂停处理数据,然后将自己当前的状态制作成快照,并将快照保存到持久化存储中,最后向CheckPointCoordinator报告自身快照情况,同时向自身所有下游算子广播该barrier,恢复数据处理。
4.每个算子按照步骤3不断制作快照、保存、广播,直到最后barrier传递到sink算子,快照制作完成。
5.当CheckPointCoordinator收到所有的算子报告之后,认为该周期的快照制作完成,否则,在规定的时间内,没有收到所有算子的报告,则认为本周期的快照制作失败。
单流的Barrier
Flink分布式快照的核心概念之一就是
数据栅栏(barrier)
。
这些barrierb被插入到数据流中,作为数据流的一部分和数据一起向下流动
barrier不会干扰数据,数据流严格有序,barrier永远不会赶超数据流,严格按照顺序。
一个barrier把数据流分割成两部分:一部分进入快照,另一部分进入下一个快照
每一个barrier都带有快照ID,并且barrier之前的数据都进入了此快照。
barrier不会干扰数据的处理,所以非常轻量。
多个不同快照的多个barrier会在流中同时出现,即多个快照同时创建。
两个输入源checkpoint实现流程
如果一个算子有两个数据输入源,其中一个barrier的先到时,会暂时阻塞该输入源,等到另一个数据输入源相同编号的barrier到来时,在制作自身快照并向下游广播该barrier。
CheckPoint持久化
CheckPoint持久化存储有三种方式
1.MemoryStateBackend
MemoryStateBackend是将状态维护在Java堆上的一个内部状态后端。键值状态和窗口算子使用Hash表来存储数据(vlaues)和定时器(timers)。当应用程序checkpoint时,此后端会将状态发给JobManager之前快照下状态,JobManager也将状态存储在Java堆上。默认情况下,MemoryStateBacked配置支持异步快照。异步快照可以避免阻塞数据流的处理,从而避免反压的发生。
在使用MemoryStateBacked注意事项:
1.默认情况下,每一个状态大小限制为5M。可以通过MemoryStateBacked的构造函数来设置这个大小。
2.状态大小收到akka帧大小的限制,所以无论怎么调整状态限制的大小,都不能超过akka帧大小。
3.状态总大小不能超过JobManager的内存。
何时使用MemoryStateBacked:
1.本地开发或调试时建议使用MemoryStateBacked,因为这种场景的状态大小是有限的
2.MemoryStateBacked最适合小状态应用场景。例如:Kafka consumer,或者一次紧记录一个函数(map、flatMap、Filter)
2.FsStateBackend
该持久化存储主要将快照存储到文件系统上,目前支持存储到HDFS和本地文件系统。如果是存储带HDFS,以hdfs://开头的路径(new FsStateBackend(“hdfs://…”)),如果是本地文件系统,以file://开头(new FsStateBacked(“file://…”))。分布式情况下不建议存储到本地,某个任务在节点A上失败,在节点B上恢复数据,B节点无法读取到A上的数据,导致状态恢复失败。
3.RocksDBStateBacked
RocksDBStateBacked的配置也需要一个文件系统(类型、地址、路径)如下所示:
- “hdfs://namenode:9000/flink/checkpoints”
- “file:///flink/checkpoints”
RocksDB是一种嵌入式本地数据库,RocksDBStateBacked将处理的数据存储到RocksDB的本地磁盘中,在触发CheckPoint时,会将整个RocksDB数据库存储到配置的文件系统中,或者在Flink超大作业时可以将增量的数据存储到配置的文件系统中。同时Flink会将极少的元数据存储到JobManager或者zookeeper(HA)中。RocksDB默认也是异步存储快照。
使用的场景:
- RocksDBStateBacked适用于大作业、长窗口和大键值状态的的有状态的任务。
- RocksDBstateBacked非常适合高可用方案。
- RocksDBStateBacked是唯一支持增量checkpoint的后端,非常适用于超大状态的场景。
当使用RocksDBStateBacked的时,状态大小只受限于磁盘可用空间大小的限制,RocksDBStateBacked是管理超大状态的最佳选择。使用RocksDBStateBacked的权衡点在于所有与状态相关的操作是否需要序列化(反序列化)参能跨越JNI边界。与上面堆上后端相比,这可能影响应用程序的吞吐量。
如果用户使用自定义窗口(window),不推荐使用RocksDBStateBacked。在自定义窗口中,状态ListState保存在StateBacked中,如果读取一个key值中多个value值,则RocksDB读取该种ListState非常缓慢。
设置CheckPoint的代码如下:
val env = StreamExecutionEnvironment.getExecutionEnvironment
// start a checkpoint every 1000ms
env.enableCheckpoint(1000)
// advanced options
// 设置checkpoint的执行模式,最多执行一次或者至少执行一次
env.getCheckpointConfig.setCheckPointMode(CheckpointMode.EXECTLY_ONCE)
// 设置checkpoint超时时间
env.getCheckpointConfig.setCheckpointTimeout(60000)
// 如果制作快照过程中出现错误,整体任务是否失败True是 Flase不是
env.getCheckpointConfig.setFailOnCheckpointingError(false)
// 设置同一个时间有多少个checkpoint可以同时执行
env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
修改state backed的两种方式
第一种:
env.setStateBacked(new FsStateBacked("hdfs的路径"))
或者 new MemoryStateBackend()
或者 new RocksDBStateBackend(filebackend, true);
第二种:
修改flink-conf.yaml
state.backend:filesystem
state.checkpoint.dir:hdfs://namenode:9000/flink/checkpoints
注意:statr.backend的值可以是以下几种:
jobmanager(MemoryStateBackend),
filesystem(FsStateBackend),
rocksdb(RocksDBStateBackend)
checkpoint案例
import org.apache.flink.runtime.state.filesystem.FsStateBackend
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.api.scala._
object CheckpointDemo:
def main(args: Array[String]):Unit = {
// 1.初始化运行环境
val env:StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
//2.开启checkpoint
env.enableCheckpointing(5000)
//指定checkpoint存储位置
if(args.length<1){
env.setStateBackend(new FsStateBackend("存储路径"))
}else{
env.setStateBacken(new FsStateBackend(args(0)))
}
// 3.指定数据源
val lines = env.sockendTextStream("node01",9999)
//4.切分单词
val result = lines.flatmap(_.split("\\s+")).map(_ -> 1).KeyBy(0).sum(1)
result.print()
//5.执行任务
env.execute()
}
实验
在flink的web ui中canal掉上面运行的任务,观察下HDFS上的checkpoint目录
会发现chk-*目录已经消失掉了,那如果我们想保留下该目录该怎办呢
Externalized Checkpoints
默认情况下,checkpoint不是持久化的,只用于从故障中恢复作业。当程序被取消时,它们会被删除。但是你可以配置checkpoint被周期性持久化到外部,类似于savepoints
。这些外部的checkpoints将它们的元数据输出到外部持久化存储并且当作业失败时不会自动清除。这样,如果你的工作失败了,你就会有一个checkpoint来恢复。
val config: CheckpointConfig = env.getCheckpointConfig
config.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)
ExternalizedCheckpointCleanup
模式配置当你取消作业时外部checkpoint会产生什么行为:
- ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION: 当作业被取消时,保留外部的checkpoint。注意,在此情况下,您必须手动清理checkpoint状态。
- ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION: 当作业被取消时,删除外部化的checkpoint。只有当作业失败时,检查点状态才可用。
一、Flink的重启策略
当Flink任务出现故障时,Flink需要重启发生故障的task及其他受影响的任务,使作业恢复正常状态。
Flink通过重启策略和故障恢复策略来重启Task任务:重启策略决定是否可以重启和重启时间间隔;故障恢复决定哪些任务需要重启。
Flink作业如果没有定义重启策略,则遵循集群启动时默认的重启策略。
默认的重启策略是通过Flink-conf.yaml文件指定的,restart-strategy定义了哪种策略被采用。如果checkpoint未启动,就会采用no restart策略,如果设置了检查点,不设置重启策略,默认是采用fixed-delay策略,重试Integer.MAX_VALUE次。
重启策略 | restart-strategy配置项 |
---|---|
固定延时重启策略 | fixed-delay |
故障率重启策略 | failure-rate |
不重启策略 | none |
除了定义一个默认故障重启策略之外,你可以为每一个job设置重启策略,这个策略可以在ExecutionEnvironment中调用SetRestartStrategy()方法程序化的调用,这种方法同样适用于StreamExecutionEnvironment环境中。
1.固定延时重启策略
固定延时重启策略按照指定的次数重启作业。如果尝试超过了给定的重启次数,作业最终将失败,两次作业重启之间会间隔固定的时间。
配置参数 | 描述 | 默认配置项 |
---|---|---|
restart-strategy.fixed-delayattemps | 作业宣告失败之前,Flink程序重试执行的最大次数 | 启用chckpoint的话时Integer.MAX_VALUE,否则是1 |
restart-stratdgy.fixed-delay.delay | 延时重试意味着任务遭遇故障时,并不立即重新启动,而是延后一段时间。当程序与外部系统交互时延时重试会有所帮助,比如程序有连接或者挂起的事务的话,在尝试重新启动任务之前等待事务的连接或者挂起的事务超时 | 启用checkpoint的话是10S,否则使用akka.ask.timeout的值 |
restart-strategy.fixed-delay.attempts: 3
restart-strategy.fixed-delay.delay: 10 s
val env = ExecutionEnvironment.getExecutionEnvironment()
env.setRestartStrategy(RestartStrategies.fixedDelayRestart(
3, // 尝试重启的次数
Time.of(10, TimeUnit.SECONDS) // 延时
))
2.故障率重启策略
故障率在作业发生故障之后重启作业,但是当故障率(每个时间间隔发生故障的次数)超过设定的限制时,作业最终会失败。在连续两次重启尝试之间,重启策略等待一段固定长度的时间。
通过flinl-yaml.conf中设置如下配置参数,默认启用此策略
restart-strategy: failure-rate
配置参数 描述 | 配置默认值 | |
---|---|---|
restart-strategy.failure-rate.max-failures-per-interval | 单个时间间隔允许最大重启的次数 | 1 |
restart-strategy.failure-rate.failure-rate-interval | 测量故障率的时间间隔 | 1分钟 |
restart-strategy.failure-rate.delay | 连续两次重启尝试之间的延时 | akka.ask.timeout |
restart-strategy.failure-rate.max-failures-per-interval: 3
restart-strategy.failure-rate.failure-rate-interval: 5 min
restart-strategy.failure-rate.delay: 10 s
val env = ExecutionEnvironment.getExecutionEnvironment()
env.setRestartStrategy(RestartStrategies.failureRateRestart(
3, // 每个时间间隔的最大故障次数
Time.of(5, TimeUnit.MINUTES), // 测量故障率的时间间隔
Time.of(10, TimeUnit.SECONDS) // 延时
))
3.不重启策略
作业失败,不尝试重启
restart-strategy: none
在程序中设置
val env = ExecutionEnvironment.getExecutionEnvironment()
env.setRestartStrategy(RestartStrategies.noRestart())
二、checkpoint综合案例
需求:
每隔1秒统计最近4秒中窗口的数据,然后对统计的结果进行checkpoint处理
数据规划:
使用自定义算子每秒钟产生大约10000条数据。
产生的数据为一个四元组(Long,String,String,Integer)—(id,name,info,count)数据经统计后,统计结果打印到终端输出打印输出的结果为Long类型的数据
开发思路:
source算子每隔1秒钟发送10000条数据,并注入到window中
window算子每隔1秒钟统计一次最近4秒钟内的数据量
每隔1秒钟将统计结果打印在终端
每隔6秒钟触发一次检查点,然后将检查点的结果存储到HDFS或者本地文件种
ListCheckpoint定义了两个接口,一个snapshotState,一个是restoreState方法
snapshotState方法有一个checkpointid参数,是唯一单调递增的数字,而timestamp则是master触发checkpoint的时间戳,该方法要返回当前的state(List结构)
restoreState方法会在failure recovery的时候调用,传递的参数为List类型的state,方法里头可以将state恢复到本地。
开发步骤:
1.自定义数据源(id:Long,name:String,info:String,count:Int)
2.自定义数据源,继承RichSourceFunction
3.实现run方法,每秒钟向流中注入10000个样例类
开发自定义状态:
1.继承Serializable
2.为总数提供set和get方法
开发自定义window和检查点:
1.继承WindowFunction
2.重写apply方法,对窗口数据进行总数累加
3.继承ListCheckPointed
4.重写snapshotState,制作自定义快照
5.重写restoreState,恢复自定义快照
开发主业务:
1.流处理环境
2.开启checkpoint,间隔为6秒
3.设置checkpoint位置
4.设置处理时间为事件时间
5.添加数据源
6.添加水印支持
7.keyby分组
8.设置滑动窗口,窗口时间为6秒
9.指定自定义窗口
10.打印结果
11.执行任务
样例类:
package com.sanshi.flink.checkpoint
case class SEvent(id:Long, name:String, info:String, count:Int)
模拟数据源
object DataSourceTest {
class SEventSourceWithChk extends RichSourceFunction[SEvent]{
private var isrunning = true
// 数据源产生数据的方法,每秒钟向流中发送10000条数据
override def run(sourceContext: SourceFunction.SourceContext[SEvent]): Unit = {
while (isrunning){
for (i <- 0 until 10000){
sourceContext.collect(SEvent(1, "Hi~" + i, "info~", 1))
}
TimeUnit.SECONDS.sleep(1)
}
}
// 任务停止时调用该方法,停止生产数据
override def cancel(): Unit = {
isrunning = false
}
}
/**
* 该数据在算子制作快照时用于保存到目前为止算子记录的数据条数。
* 用户自定义状态
*/
class UDFState extends Serializable {
private var count = 0L
// 设置用户自定义状态
def setState(s: Long) = count = s
// 获取用户自定状态
def getState = count
}
/**
* 此段代码时window算子的代码,每当触发计算时统计窗口中的元组数量
*/
class WindowStatisticWithChk extends WindowFunction[SEvent, Long, Tuple, TimeWindow] with ListCheckpointed[UDFState]{
private var total = 0L
/**
* window算子的实现逻辑,即:统计window中元组的数量
* @param key keyby的类型
* @param window 窗口
* @param input 输入类型
* @param out 输出类型
*/
override def apply(key: Tuple, window: TimeWindow, input: Iterable[SEvent], out: Collector[Long]): Unit = {
var count = 0L
for (event <- input) {
count += 1L
}
total += count
out.collect(count)
}
/**
* 制作自定义状态快照
* @param checkpoindId 唯一单调递增的数字
* @param timestamp master触发checkpoint的时间戳
* @return
*/
override def snapshotState(checkpoindId: Long, timestamp: Long): util.List[UDFState] = {
val udfList: java.util.ArrayList[UDFState] = new java.util.ArrayList[UDFState]
val udfState = new UDFState
udfState.setState(total)
udfList.add(udfState)
udfList
}
/**
* 从自定义快照中恢复状态
* @param state
*/
override def restoreState(state: util.List[UDFState]): Unit = {
val udfState = state.get(0)
total = udfState.getState
}
}
object FlinkEventTimeAPIChkMain {
def main(args: Array[String]): Unit = {
// 1.构建流式运行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 2.开启checkpoint,时间间隔为6S
env.enableCheckpointing(6000)
// 3.设置检查点存储位置
env.setStateBackend(new FsStateBackend("file://"))
// 4.
env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)
// 5.设置处理时间为事件时间
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
// 导入隐式转换
import org.apache.flink.api.scala._
// 6.接入数据源
val inputDataStream: DataStream[SEvent] = env.addSource(new SEventSourceWithChk)
// 7.添加水印,用于处理延迟到来数据
val watermarDataStream: DataStream[SEvent] = inputDataStream.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks[SEvent] {
// 设置watermark
override def getCurrentWatermark: Watermark = {
new Watermark(System.currentTimeMillis())
}
// 给每个元组加上时间戳
override def extractTimestamp(t: SEvent, l: Long): Long = {
System.currentTimeMillis()
}
})
// 8.keyby分组
val keyedDataStream: KeyedStream[SEvent, Tuple] = watermarDataStream.keyBy("id")
// 9.设置滑动窗口,窗口的时间大小为4秒
val windowDataStream: WindowedStream[SEvent, Tuple, TimeWindow] = keyedDataStream.timeWindow(Time.seconds(4), Time.seconds(1))
// 10.指定自定义窗口
val result: DataStream[Long] = windowDataStream.apply(new WindowStatisticWithChk)
// 11.打印结果
result.print()
// 12.执行任务
env.execute()
}
}
三、savepoint
Savepoint是依据checkpoint机制所创建的Flink流作业运行状态的一致性镜像。savepoint可以用来作业的重启、更新和程序升级等。savepoint由两部组成,一部分是存储在HDFS、s3等稳定存储在文件系统的二进制目录,另一部分是元数据文件,相对较小。
- savepoint和checkpoint的区别
作用 | 实现方式 | 生命周期 | |
---|---|---|---|
checkpoint | 程序由意外导致的作业失败,checkpoint依据重启策略恢复作业正常运行 | 轻量快速 | 默认是Flink程序自身控制 |
savepoint | 手动备份/重启/恢复作业 | 注重可移植性,成本较高 | 用户手动控制 |