Flink Checkpoint 机制

Checkpoint是Flink实现容错机制最核心的功能,它能够根据配置周期性地基于Stream中各个Operator/task的状态来生成快照,从而将这些状态数据定期持久化存储下来,当Flink程序一旦意外崩溃时,重新运行程序时可以有选择地从这些快照进行恢复,从而修正因为故障带来的程序数据异常。

CheckPoint原理

Flink的检查点机制实现了标准的Chandy-Lamport(分布式快照)算法,用来实现分布式快照。Flink的检查点特性在流处理器中是独一无二的,程序运行时有flink自动生成,它使得flink可以准确的维持状态,实现数据的一致性(exactly-once),并且高效的重新处理数据。
Flink的容错机制的核心部分是制作分布式数据流和操作算子状态的一致性快照。在分布式快照当中,有一个核心的元素:Barrier。屏障作为数据流的一部分随着记录被注入到数据流中。屏障永远不会赶超通常的流记录,它会严格遵循顺序。屏障将数据流中的记录隔离成一系列的记录集合,并将一些集合中的数据加入到当前的快照中,而另一些数据加入到下一个快照中。每一个屏障携带着快照的ID,快照记录着ID并且将其放在快照数据的前面。屏障不会中断流处理,因此非常轻量级。来自不同快照的多个屏障可能同时出现在流中,这意味着多个快照可能并发地发生。
Checkpoint能够根据配置周期性地基于Stream中各个Operator/task的状态来生成快照,从而将这些状态数据定期持久化存储下来,当Flink程序一旦意外崩溃时,系统可以在发生故障时回滚,重新运行程序时可以有选择地从这些快照进行恢复,从而修正因为故障带来的程序数据异常。
barriers数据流严格有序,barrier将数据流中的记录隔离成一系列的记录集合。这些barrier被注入数据流并与记录一起作为数据流的一部分向下流动。每个barriers都携带了快照的ID,快照的数据在barriers的前面推送。barriers非常轻量级,不会中断流的流动。同一时间,会有多个checkpoint在并发进行。核心思想是在 input source 端插入 barrier,控制 barrier 的同步来实现 snapshot 的备份和 exactly-once 语义。
barrier被注入到并行流的数据源,注入快照n (称为Sn)的barriers 是数据源中的一个位置,在kafka 就是某个分区的最后一条记录的offset。这个位置Sn后续会汇报给JM的checkpoint 协调器。barrier随着流向下游流动,当中间的operator从他所有的输入流中收到checkpoint n 的barrier时,该operator会将barrier发送给他的下游operator。一旦到达流式DAG的末端,sink算子会将这条流的state handle汇报JM的checkpoint 协调器,在所有sink确认快照后,意味快照已完成,状态存储到相应的state backend(存储介质)中。
此时这些记录(及其后续记录)将已经通过整个数据流拓扑,也即是已经被处理结束。
barrier 对齐
 
 
当一个opeator有多个输入流的时候,checkpoint barrier n 会进行对齐,就是已到达的会先缓存到buffer里等待其他未到达的,一旦所有流都到达,则会向下游广播,exactly-once 就是利用这一特性实现的,at least once 因为不会进行对齐,就会导致有的数据被重复处理。
分布式快照 Checkpoint 完成后,当作业发生故障了如何去恢复?
假如作业分布跑在 3 台机器上,其中一台挂了。这个时候需要把进程或者线程移到 active 的 2 台机器上,此时还需要将整个作业的所有 Task 都回滚到最后一次成功 Checkpoint 中的状态,然后从该点开始继续处理。
如果要从 Checkpoint 恢复,必要条件是数据源需要支持数据重新发送。Checkpoint恢复后, Flink 提供两种一致性语义,一种是恰好一次,一种是至少一次。在做 Checkpoint时,可根据 Barries 对齐来判断是恰好一次还是至少一次,如果对齐,则为恰好一次,否则没有对齐即为至少一次。如果只有一个上游,也就是说 Barries 是不需要对齐的的;如果只有一个 Checkpoint 在做,不管什么时候从 Checkpoint 恢复,都会恢复到刚才的状态;如果有多个上游,假如一个上游的 Barries 到了,另一个 Barries 还没有来,如果这个时候对状态进行快照,那么从这个快照恢复的时候其中一个上游的数据可能会有重复。

CheckPoint流程

在checkpoint过程中,JobManager会按照代码配置的规则,定期触发checkpoint,在所有的source的子任务中注入checkpoint Barrier,TaskManager在收到所有上游广播的CheckpointBarrier 后,触发checkpoint。当整个DAG图的子任务的checkpoint都做完之后,会汇报给JobManager,JobManager则认为这个checkpoint已经完成。
一个 Task 的 Checkpoint 流程包括以下几个步骤:
  1. JobManager 向 Source 算子发送 Barrier ,初始化 Checkpoint;
  2. Source 算子收到 Barrier 之后,Checkpoint 自己的 state,并向下游发送 Barrier;
  3. 下游收到 Barrier 后,进行 Barrier Alignment 处理;
  4. Task 开始同步阶段的 Snapshot;
  5. Task 开始异步阶段的 Snapshot;
  6. Task 做完 Checkpoint 之后,再上报 JobManager。
 
开启 CheckPoint功能
检查点是Flink实现 exactly-once 语义的核心机制,启用检测点,需要:
(1) 支持的历史记录外部数据源, 如kafka 和分布式文件系统
(2) 可持久化状态的外部存储, 如分布式文件系统。
检查点默认是关闭的,启用检查点需要配置 一致性级别。
想要使用 Flink Checkpoint 功能,首先是要在实时任务开启 Checkpoint。Flink 默认情况下是关闭 Checkpoint 功能,下面代码是开启 Checkpoint :
上述代码中,设置了 Flink Checkpoint 的间隔 3 秒,设置的 Checkpoint 的语义为 EXACTLY_ONCE。Flink 默认的 Checkpoint 语义为 EXACTLY_ONCE。
如何设置配置CheckPoint
默认checkpoint功能是disabled的,想要使用的时候需要先启用。checkpoint开启之后,默认的checkPointMode是Exactly-once。
checkpoint的checkPointMode有两种:
  • Exactly-once: 数据处理且只被处理一次
  • At-least-once:数据至少被处理一次
Exactly-once对于大多数应用来说是最合适的。At-least-once可能用在某些延迟超低的应用程序(始终延迟为几毫秒)

Flink Checkpoint 语义

Flink Checkpoint支持两种语义,Exactly_Once 和 At_Least_Once。这两种语义的区别主要在于对barrier 对齐方式的处理。 Flink默认的 Checkpoint 语义是 Exactly_Once。
  • Exactly_Once的算子在接受到第一个barrier后会阻塞上游输入通道,将输入数据缓存起来,直到所有上游的barrier都收到之后才会继续处理这些数据。
  • At_Least_Once则不会阻塞上游数据,即使没有对齐barrier也会处理后续的数据。
理解:
Exactly_Once 含义:
保证每条数据对于 Flink 任务的状态结果只影响一次。打个比方,比如 WordCount 程序,目前实时统计的 “hello” 这个单词数为 5,同时这个结果在这次 Checkpoint 成功后,保存在了 HDFS。在下次 Checkpoint 之前, 又来 2 个 “hello” 单词,突然程序遇到外部异常自动容错恢复,会从最近的 Checkpoint 点开始恢复,那么会从单词数为 5 的这个状态点开始恢复,Kafka 消费的数据点位也是状态为 5 这个点位开始计算,所以即程序遇到外部异常自动恢复时,也不会影响到 Flink 状态的结果计算。
At_Least_Once 含义:
每条数据对于 Flink 任务的状态计算至少影响一次。比如在 WordCount 程序中,你统计到的某个单词的单词数可能会比真实的单词数要大,因为同一条消息,当 Flink 任务容错恢复后,可能将其计算多次。
 
exactly-once语义实现:两阶段提交机制
Flink 抽象了 TwoPhaseCommitSinkFunction 来帮助用户更好地实现 exactly-once 语义。
在使用两阶段提交算子时,我们可以继承TwoPhaseCommitSinkFunction这个虚拟类。
通过一个简单的写文件的例子来解释一下这个虚拟类。这个两步提交的类有四个状态。
1. 开始事物(beginTransaction)- 创建一个临时文件夹,来写把数据写入到这个文件夹里面。
2. 预提交(preCommit)- 将内存中缓存的数据写入文件并关闭。
3. 正式提交(commit)- 将之前写完的临时文件放入目标目录下。这代表着最终的数据会有一些延迟。
4. 丢弃(abort)- 丢弃临时文件
若失败发生在预提交成功后,正式提交前。可以根据状态来提交预提交的数据,也可删除预提交的数据。
(Flink 提供了CheckpointedFunction与CheckpointListener这样两个接口,CheckpointedFunction中有snapshotState方法,每次checkpoint触发执行方法,通常会将缓存数据放入状态中,可以理解为一个hook,这个方法里面可以实现预提交,CheckpointListyener中有notifyCheckpointComplete方法,checkpoint完成之后的通知方法,这里可以做一些额外的操作。
例如FlinkKafkaConumerBase使用这个来完成Kafka offset的提交,在这个方法里面可以实现提交操作。在2PC中提到如果对应流程例如某个checkpoint失败的话,那么checkpoint就会回滚,不会影响数据一致性,那么如果在通知checkpoint成功的之后失败了,那么就会在initalizeSate方法中完成事务的提交,这样可以保证数据的一致性。最主要是根据checkpoint的状态文件来判断的)
posted @ 2022-12-28 14:25  司也  阅读(1176)  评论(0)    收藏  举报