检查点(Checkpoint)
发生故障之后怎么办?最简单的想法当然是重启机器、重启应用。由于是分布式的集群,
即使一个节点无法恢复,也不会影响应用的重启执行。这里的问题在于,流处理应用中的任务
都是有状态的,而为了快速访问这些状态一般会直接放在堆内存里;现在重启应用,内存中的
状态已经丢失,就意味着之前的计算全部白费了,需要从头来过。就像编写文档或是玩
RPG
游戏,因为宕机没保存而要重来一遍是一件令人崩溃的事情;这种惨痛的经历让我们养成了一
个好习惯——随时存档,这样即使遇到宕机也可以读档继续了
![](https://i-blog.csdnimg.cn/blog_migrate/6f5423821961eabaa4be6e64559e9c83.png)
检查点是
Flink
容错机制的核心。这里所谓的“检查”,其实是针对故障恢复的结果而言
的:故障恢复之后继续处理的结果,应该与发生故障前完全一致,我们需要“检查”结果的正
确性。所以,有时又会把
checkpoint
叫作“一致性检查点”。
检查点的保存
周期性的触发保存
检查点的保存,最关键的就是要等所有任务将“同一个数据”处理完毕。下面我们通过一
个具体的例子,来详细描述一下检查点具体的保存过程。
回忆一下我们最初实现的统计词频的程序——
WordCount
。这里为了方便,我们直接从数
据源读入已经分开的一个个单词,例如这里输入的就是:
“
hello
”“
world
”“
hello
”“
flink
”“
hello
”“
world
”“
hello
”“
flink
”……
对应的代码就可以简化为:
SingleOutputStreamOperator<Tuple2<String, Long>> wordCountStream =
env.addSource(...)
.map(word -> Tuple2.of(word, 1L))
.returns(Types.TUPLE(Types.STRING, Types.LONG));
.keyBy(t -> t.f0);
这里的意思就是在source为3的时候如果在路上的hello处理完,并且修改状态为2成功以后,那么就可以直接保存检查点了
数据发生故障的时候
这个时候机器重启
由于上一次出现 故障的时候,同一个数据没有处理完成,那么读取的就是之前保存的没有出现故障时候的状态
为了不丢数据,我们应该从保存检查点后开始重新读取数据,这可以通过
Source
任务向
外部数据源重新提交偏移量(
offset
)来实现
![](https://i-blog.csdnimg.cn/blog_migrate/1bfdde309f054ace838e4ac8b4bf246d.png)
继续处理数据
当处理到第
5
个数据时,就已经追上了发生故障时的系统状态。之后继续处理,就好像没
有发生过故障一样;我们既没有丢掉数据也没有重复计算数据,这就保证了计算结果的正确性。
在分布式系统中,这叫作实现了“精确一次”(
exactly-once
)的状态一致性保证。
状态的拓扑结构在
JobManager
上
可以由
JobGraph
分析得到,而检查点保存的定期触发也是由
JobManager
控制的;所以故障恢
复的过程需要
JobManager
的参与
检查点算法
检查点分界线算法
分布式快照算法
检查点的配置
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
// 启用检查点,间隔时间 1 秒
env.enableCheckpointing(1000);
CheckpointConfig checkpointConfig = env.getCheckpointConfig();
// 设置精确一次模式
checkpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 最小间隔时间 500 毫秒
checkpointConfig.setMinPauseBetweenCheckpoints(500);
// 超时时间 1 分钟
checkpointConfig.setCheckpointTimeout(60000);
// 同时只能有一个检查点
checkpointConfig.setMaxConcurrentCheckpoints(1);
// 开启检查点的外部持久化保存,作业取消后依然保留
checkpointConfig.enableExternalizedCheckpoints(
ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
// 启用不对齐的检查点保存方式
checkpointConfig.enableUnalignedCheckpoints();
291
// 设置检查点存储,可以直接传入一个 String,指定文件系统的路径
checkpointConfig.setCheckpointStorage("hdfs://my/checkpoint/dir")
保存点
保存点就是手动触发检查点
需要注意的是,保存点能够在程序更改的时候依然兼容,前提是状态的拓扑结构和数据类
型不变。我们知道保存点中状态都是以算子
ID-
状态名称这样的
key-value
组织起来的,算子
ID
可以在代码中直接调用
SingleOutputStreamOperator
的
.uid()
方法来进行指定:
DataStream<String> stream = env
.addSource(new StatefulSource())
.uid("source-id")
.map(new StatefulMapper())
.uid("mapper-id")
.print();
对于没有设置
ID
的算子,
Flink
默认会自动进行设置,所以在重新启动应用后可能会导致
ID
不同而无法兼容以前的状态。所以为了方便后续的维护,强烈建议在程序中为每一个算子
手动指定
ID
。