一、检查点
Checkpoint
1. 介绍
- 有状态流应用中的检查点(checkpoint),其实就是所有任务的状态在某个时间点的一个快照(一份拷贝),这个时间点应该是所有任务都恰好处理完一个相同的输入数据的时刻。
- 在一个流应用程序运行时, Flink 会定期保存检查点,在检查点中会记录每个算子的 id 和状态;如果发生故障,Flink 就会用最近一次成功保存的检查点来恢复应用的状态,重新启动处理流程,就如同“读档”一样。
- 检查点是 Flink 容错机制的核心。这里的“检查”是指故障恢复之后继续处理的结果,应该与发生故障前完全一致,所以需要“检查”结果的正确性。因此又会把 checkpoint 叫作“一致性检查点”。
2. 检查点保存
-
在 Flink 中,检查点的保存是周期性触发的,间隔时间可以进行设置,当每隔一段时间检查点保存操作被触发时,就把每个任务当前的状态复制一份,按照一定的逻辑结构放在一起持久化保存起来,就构成了检查点。
-
当检查点保存操作被触发时,保存状态的时机是当所有任务都恰好处理完一个相同的输入数据的时候。首先,这样避免了除状态之外其他额外信息的存储,提高了检查点保存的效率。其次,一个数据要么就是被所有任务完整地处理完,状态得到了保存;要么就是没处理完,状态全部没保存,相当于构建了一个“事务”(transaction。
-
以 WordCount 为例,具体的检查点保存流程为:源(Source)任务从外部数据源读取数据,并记录当前的偏移量,作为算子状态(Operator State)保存下来;Sum 算子会把当前求和的结果作为按键分区状态(Keyed State)保存下来;当触发了检查点保存时,所有任务都已经处理了 3 条数据 (“hello”,“world”,“hello”),此时就会将 Source 算子的偏移量 3 和 sum 算子的状态 (“hello” -> 2,“world” -> 1) 保存成一个检查点,写入外部存储中。具体的存储系统由状态后端的配置项 “检查点存储” (CheckpointStorage) 来决定的,有作业管理器的堆内存(JobManagerCheckpointStorage)和文件系统(FileSystemCheckpointStorage)两种选择。一般情况下,会将检查点写入持久化的分布式文件系统。
3. 检查点故障恢复
-
当 Flink 任务发生故障时,会使用最近的检查点来一致恢复应用程序的状态,并重新启动处理流程,发生故障时正在处理的所有数据都需要重新处理,所以需要让源(source)任务向数据源重新提交偏移量、请求重放数据,因此需要外部数据源能够重置偏移量,如 Kafka。
-
以 WordCount 为例,具体的检查点故障恢复流程为:
-
程序之前在处理完 (“hello”,“world”,“hello”) 三个数据后保存了一个检查点,之后又正常处理了一个数据 (“flink”),但在处理第五个数据 (“hello”) 时发生了故障,此时 Source 任务已经处理完毕且偏移量为 5,Map 任务也处理完成,而 sum 任务在处理中发生了故障,此时状态并未保存
-
第一步是重启发生故障的应用,此时所有任务的状态会清空
-
第二步是找到最近一次保存的检查点,从中读出每个算子任务状态的快照,分别填充到对应的状态中。此时,Flink 内部所有任务的状态,都恢复到了保存检查点的那一时刻,即刚好处理完 (“hello”,“world”,“hello”) 三个数据的时候;注意,虽然故障发生前 (“flink”) 数据已经处理完,但由于没保存在检查点中,所以恢复后的任务中并没有 (“flink”) 的处理结果
-
第三步是通过 Source 任务向外部数据源重新提交偏移量(offset)来实现从保存检查点后开始重新读取数据,由于从检查点恢复状态时之前所有在检查点保存后到发生故障前的这段时间内的数据 (“flink”,“hello”) 都丢失了,为了重新处理这些丢失的数据,必须从数据源重放这些数据。
-
第四步是重放第 4、5 个数据后,然后继续读取后面的数据正常处理,此时,既没有丢掉数据也没有重复计算数据,这就保证了计算结果的正确性。在分布式系统中,这叫作实现了“精确一次”(exactly-once)的状态一致性保证。
-
4. 检查点算法
Flink 采用了基于 Chandy-Lamport 算法的分布式快照保存检查点
4.1 Checkpoint Barrier
检查点分界线或检查点屏障
- 类似于 watermark,Checkpoint Barrier 是一种在数据流中插入的特殊数据结构,用来表示触发检查点保存的时间点,会把一条流上的数据按照不同的检查点分隔开。
- Checkpoint Barrier 是由 JobManager 生成的在 Source 算子中注入到常规数据流中,它的位置是限定好的, 不能超过其他数据,也不能被后面的数据超过。检查点分界线中带有一个检查点 ID,这是当前要保存的检查点的唯一标识。
- Checkpoint Barrier 能够保证在不暂停流处理的前提下,让每个任务“认出”触发检查点保存的那个数据,当一个任务遇到一个 Checkpoint Barrier 时就开始对状态做持久化快照保存。由于数据流是保持顺序依次处理的,因此遇到这个标识就代表之前的数据都处理完了,可以保存一个检查点;而在这个标识之后的数据引起的状态改变就不会体现在这个检查点中,而需要保存到下一个检查点中
4.2 具体算法过程
-
案例:一个有两个输入流的应用程序,用并行的两个 Source 任务来读取自然数,然后按照奇偶数进行分组后进行 sum 操作,最终输出结果
-
在 JobManager 中有一个“检查点协调器”(checkpoint coordinator),专门用来协调处理检查点的相关工作。检查点协调器会周期性地向每个 TaskManager 发送一条带有新检查点 ID 的消息,通过这种方式来启动检查点
-
收到指令后,TaskManager 会将带有检查点 ID 的分界线(barrier)插入到当前的数据流中并向下游传递,同时让所有的 Source 任务把自己的偏移量(算子状态)保存起来,状态后端在状态存入检查点之后,会返回通知给 source 任务,source 任务就会向 JobManager 确认检查点完成