【Flink状态管理五】Checkpoint的设计与实现
文章目录
- 1. Checkpoint的整体设计
- 2. Checkpoint创建源码解析
- 2.1. DefaultExecutionGraphBuilder.buildGraph
- 2.2. ExecutionGraph.enableCheckpointing
由于系统原因导致Flink作业无法正常运行的情况非常多,且很多时候都是无法避免的。对于Flink集群来讲,能够快速从异常状态中恢复,同时保证处理数据的正确性和一致性非常重要。Flink主要借助Checkpoint的方式保障整个系统状态数据的一致性,也就是基于ABS算法实现轻量级快照服务。
本节我们详细了解Checkpoint的设计与实现。
1. Checkpoint的整体设计
Checkpoint的执行过程分为三个阶段:启动、执行以及确认完成。其中Checkpoint的启动过程由JobManager管理节点中的CheckpointCoordinator组件控制,该组件会周期性地向数据源节点发送执行Checkpoint的请求,执行频率取决于用户配置的CheckpointInterval参数。
执行过程:
- 在JobManager管理节点通过CheckpointCoordinator组件向每个数据源节点发送Checkpoint执行请求,此时数据源节点中的算子会将消费数据对应的Position发送到JobManager管理节点中。
- JobManager节点会存储Checkpoint元数据,用于记录每次执行Checkpoint操作过程中算子的元数据信息,例如在FlinkKafkaConsumer中会记录消费Kafka主题的偏移量,用于确认从Kafka主题中读取数据的位置。
- 在数据源节点执行完Checkpoint操作后,继续向下游节点发送CheckpointBarrier事件,下游算子通过对齐Barrier事件,触发该算子的Checkpoint操作。
当下游的map算子接收到数据源节点的Checkpoint
Barrier事件后,首先对当前算子的数据进行处理,并等待其他上游数据源节点的Barrier事件到达。该过程就是Checkpoint
Barrier对齐,目的是确保属于同一Checkpoint的数据能够全部到达当前节点。
Barrier事件的作用就是切分不同Checkpoint批次的数据。
-
当map算子接收到所有上游的Barrier事件后,就会触发当前算子的Checkpoint操作,并将状态数据快照到指定的外部持久化介质中,该操作主要借助状态后端存储实现。
-
当状态数据执行完毕后,继续将Barrier事件发送至下游的算子,进行后续算子的Checkpoint操作。
-
另外,在map算子中执行完Checkpoint操作后,也会向JobManager管理节点发送Ack消息,确认当前算子的Checkpoint操作正常执行。此时Checkpoint数据会存储该算子对应的状态数据,如果StateBackend为MemoryStateBackend,则主要会将状态数据存储在JobManager的堆内存中。
sink节点的ack
像map算子节点一样,当Barrier事件到达sink类型的节点后,sink节点也会进行Barrier对齐操作,确认上游节点的数据全部接入。然后对接入的数据进行处理,将结果输出到外部系统中。完成以上步骤后,sink节点会向JobManager管理节点发送Ack确认消息,确认当前Checkpoint中的状态数据都正常进行了持久化操作。(之后呢?当任务结束之后,cp会消失还是?)
2. Checkpoint创建源码解析
通过调用StreamExecutionEnvironment.enableCheckpointing(),开启Checkpoint。
此时Checkpoint的配置会被存储在StreamGraph中,然后将StreamGraph中的CheckpointConfig转换为JobCheckpointingSettings数据结构存储在JobGraph对象中,并伴随JobGraph提交到集群运行。启动JobMaster服务后,JobMaster调度和执行Checkpoint操作。
2.1. DefaultExecutionGraphBuilder.buildGraph
如下代码,通过JobGraph构建ExecutionGraph的过程中,获取JobGraph中存储的JobCheckpointingSettings配置,然后创建ExecutionGraph。
1)根据snapshotSettings配置获取triggerVertices、ackVertices以及confirmVertices节点集合,并转换为对应的ExecutionJobVertex集合。
- 其中triggerVertices集合存储了所有SourceOperator节点,这些节点通过CheckpointCoordinator主动触发Checkpoint操作。
- ackVertices和confirmVertices集合存储了StreamGraph中的全部节点,代表所有节点都需要返回Ack确认信息并确认Checkpoint执行成功。
2)创建CompletedCheckpointStore组件,用于存储Checkpoint过程中的元数据。
- 当对作业进行恢复操作时会在CompletedCheckpointStore中检索最新完成的Checkpoint元数据信息,然后基于元数据信息恢复Checkpoint中存储的状态数据。CompletedCheckpointStore有两种实现,分别为StandaloneCompletedCheckpointStore和ZooKeeperCompletedCheckpointStore。
- 在CompletedCheckpointStore中通过maxNumberOfCheckpointsToRetain参数配置以及结合checkpointIdCounter计数器保证只会存储固定数量的CompletedCheckpoint。
3)创建CheckpointStatsTracker实例
用于监控和追踪Checkpoint执行和更新的情况,包括Checkpoint执行的统计信息以及执行状况,WebUI中显示的Checkpoint监控数据主要来自CheckpointStatsTracker。
4)创建StateBackend,从UserClassLoader中反序列化出应用指定的StateBackend并设定为applicationConfiguredBackend。
5)初始化用户自定义的Checkpoint Hook函数
6)最终调用executionGraph.enableCheckpointing()方法,在作业的执行和调度过程中开启Checkpoint。
// 配置状态数据checkpointing // 从jobGraph中获取JobCheckpointingSettings JobCheckpointingSettings snapshotSettings = jobGraph.getCheckpointingSettings(); //如果snapshotSettings不为空,则开启checkpoint功能 if (snapshotSettings != null) { List triggerVertices = idToVertex(snapshotSettings.getVerticesToTrigger(), executionGraph); List ackVertices = idToVertex(snapshotSettings.getVerticesToAcknowledge(), executionGraph); List confirmVertices = idToVertex(snapshotSettings.getVerticesToConfirm(), executionGraph); //创建CompletedCheckpointStore CompletedCheckpointStore completedCheckpoints; CheckpointIDCounter checkpointIdCounter; try { int maxNumberOfCheckpointsToRetain = jobManagerConfig.getInteger( CheckpointingOptions.MAX_RETAINED_CHECKPOINTS); if (maxNumberOfCheckpointsToRetain maxNumberOfCheckpointsToRetain = CheckpointingOptions.MAX_RETAINED_ CHECKPOINTS.defaultValue(); } // 通过recoveryFactory创建CheckpointStore completedCheckpoints = recoveryFactory.createCheckpointStore(jobId, maxNumberOfCheckpointsToRetain, classLoader); // 通过recoveryFactory创建CheckpointIDCounter checkpointIdCounter = recoveryFactory.createCheckpointIDCounter(jobId); } catch (Exception e) { throw new JobExecutionException(jobId, "Failed to initialize high- availability checkpoint handler", e); } // 获取checkpoints最长的记录次数 int historySize = jobManagerConfig.getInteger(WebOptions.CHECKPOINTS_HISTORY_SIZE); // 创建CheckpointStatsTracker实例 CheckpointStatsTracker checkpointStatsTracker = new CheckpointStatsTracker( historySize, ackVertices, snapshotSettings.getCheckpointCoordinatorConfiguration(), metrics); // 从application中获取StateBackend final StateBackend applicationConfiguredBackend; final SerializedValue applicationConfiguredBackend = null; } else { try { applicationConfiguredBackend = serializedAppConfigured. deserializeValue(classLoader); } catch (IOException | ClassNotFoundException e) { throw new JobExecutionException(jobId, "Could not deserialize application-defined state backend.", e); } } // 获取最终的rootBackend final StateBackend rootBackend; try { rootBackend = StateBackendLoader.fromApplicationOrConfigOrDefault( applicationConfiguredBackend, jobManagerConfig, classLoader, log); } catch (IllegalConfigurationException | IOException | DynamicCodeLoadingException e) { throw new JobExecutionException(jobId, "Could not instantiate configured state backend", e); } // 初始化用户自定义的checkpoint Hooks函数 final SerializedValue