Flink Checkpoint

1,966 阅读11分钟

Flink 的 checkpoint 基于 Chandy-lamport 算法,实现了分布式一致性快照,并提供了 exactly-once 语义

Flink Checkpoint 简介

什么是 Flink Checkpoint

Flink Checkpoint 是一种 Flink 任务执行过程中,在不中断任务的前提下,定时保存任务的所有状态数据,并可以在任务异常停止后,将状态恢复到任意保存点的机制

Flink Checkpoint 能实现什么

  • 在不中断任务的前提下,定时保存任务的所有状态数据
  • Flink 任务出错、异常停止后状态数据不丢失,重新启动任务后,自动恢复到出错之前的状态
  • 任务恢复后,数据从出错之前的位置重新开始计算,数据不重不漏 (需要数据源支持回溯)

Flink Checkpoint 保存了哪些状态数据

Flink 有两大类状态,一类是 Flink 自动管理的 Managed State,一类是用户手动管理的 Raw State,Checkpoint 保存的是Flink 自动管理的 Managed State

由于 Managed State 又由 Keyed State 和 Operator State 两个小类组成,因此也可以说 Checkpoint 保存的是 Keyed State 和 Operator State 两种类型的状态

Flink Checkpoint 原理

Flink Checkpoint 是一种分布式一致性快照的实现,通过定时备份任务中所有的状态,达到状态容错与故障恢复的的目的,具体细节如下

checkpoint barrier

checkpoint barrier 是 Flink Checkpoint 实现分布式一致性快照的核心,它是一种含有 checkpoint id 的特殊数据,每过一段时间, Job Manager 就会将 checkpoint barrier 插入到所有从数据源到 source 算子的数据流中(最开始的数据流)。

微信图片编辑_20210409102720.jpg

经过 Job Manager 的处理,从数据源出来的数据流被 checkpoint barrier 分成了一个一个的段落,每个 checkpoint barrier 到前一个 checkpoint barrier 之间的数据,都属于当前checkpoint的范围

微信截图_20210408180458.png

快照过程

我们已经知道,从数据源出来的数据流被 Job Manager 插入了 checkpoint barrier ,但具体怎么利用这个 barrier 进行状态快照呢?来看最简单的例子

image.png 如上图,operator id 为 source#1 的算子,持续的摄取数据源 kafka#1 的数据,Job Manager 则是定时往数据流中插入 checkpoint barrier,如上图,插入了一个checkpoint id 为 c1 的 barrier

image.png 数据继续往下流,当source#1 算子摄取到数据 A 后,会将数据 A 对应的 kafka partition offset 存下来,表示当前已经处理到这个位置。

当 source#1 算子摄取到数据 B 后,会将存储的 partition offset 更新为数据 B 对应的 kafka partition offset , 摄取到数据 C 后同理。

直到 source#1 算子摄取到 barrier c1 ,表示 c1 这个 checkpoint 的所有数据都处理完了,可以进行快照了

你可以把对状态进行快照的过程简单理解为填充一张表,如上图中的 checkpoint c1 快照表,source#1 算子进行快照时,会将自身的 operator id 和状态数据填充到表中,这样就算完成了快照。

填充完快照表之后,source#1 算子继续传给下游。

image.png

同上,operator#1 算子也会处理 A、B、C 数据,并将状态存储下来,直到遇到上游传递下来的 barrier c1,然后将自身的 operator id 和状态数据填充到 checkpoint c1 快照表中。

再说source#1 算子,它传递 barrier c1 后也没闲着,继续处理数据流中的 D、E、F 数据,当遇到checkpoint c2时,又将自身的 operator id 和状态数据填充到 checkpoint c2 快照表

image.png 最终,barrier c1 将流到了最后一个算子 sink#1,sink#1 将自身的 operator id 和状态数据填充到 checkpoint c1 快照表后,checkpoint c1 快照就算补充完整了,最后 flink 会将 checkpoint c1 快照存储到状态后端,一个完整的 checkpoint 流程就算结束了。

多并行度快照过程

上面的例子是一个比较简单的并行度为1的实例,Flink 是分布式架构,支持多并行度计算,在多并行度的场景下,operator 的 checkpoint 操作会与单并行度时,稍微有些不同。来看一些例子

image.png 上图是一个每个算子并行度都为2任务,任务开始时,Job Manager 会定时往所有初始流中插入 checkpoint barrier

image.png checkpoint barrier 流入 operator 后,依然同之前一样填充快照表,不同的是,该算子的并行度是多少,最终就会产生几条记录。示例中,source 算子并行度是2,因此最终就会有两条记录

注意,可能会出现 source#1 算子都处理到 c2了,而 source#2 算子还在处理 c1,但最终都会将 checkpoint c1 快照表补充完整

后面的步骤跟之前一样,我就不画了,当checkpoint c1 快照表补充完整,同样会被存储到状态后端

checkpoint barrier 的对齐与广播

上面这些例子中,每个算子都只有一个上游节点,当算子具有多个上游节点时,情况会有些不一样

image.png 如图 operator#1 算子有两个上游,source#1 和source#2,从图中可以看到,上游的两个流各有一个 c1 barrier

image.png 当 operator#1 算子收到 source#1 的 c1 barrier 时,并不会立即 Checkpoint。

做 Checkpoint 的时机是在算子处理完所有归属于该次 Checkpoint 的数据时

示例中,operator#1 算子还没有收到 source#2 的 c1 barrier ,因此它不确定 source#2 中属于 c1 barrier 的数据是否都已经处理完了

image.png 接着往下流,属于Checkpoint c2 的数据 D 也流入了 operator#1,但 operator#1 并不会立即处理它,而是把它暂存在内存中,因为属于 Checkpoint c1 的数据还没有处理完,如果现在就处理 Checkpoint c2 的数据会把状态弄脏

image.png 继续往下流,source#2 的 c1 barrier 也流入 operator#1 了,这表示 operator#1 已经处理完所有属于 Checkpoint c1 的数据, 可以进行快照了,快照的过程跟之前一样我就不在重复说了。

operator#1 快照完成之后,就可以正常处理缓存中属于其他 Checkpoint 的数据了,示例中对应的是 Checkpoint c2 的 D 和 E

Flink 把 一个算子在接收到所有上游的同一个 Checkpoint barrier 之前,把属于其他 Checkpoint 的数据先暂存起来的机制称为 checkpoint barrier 的对齐

知道了什么是checkpoint barrier 的对齐,我们再来说下什么是 checkpoint barrier 的广播

checkpoint barrier 的广播 指的是,当某个算子有多个下游时,checkpoint 完成后,会将该 checkpoint barrier 广播给所有下游算子

image.png 例如示例中,operator#1 有两个下游,在 checkpoint c1 完成后,将 c1 barrier 广播给了 sink#1 和 sink#2,示例中只有两个下游,如果还有 sink#3 的话也会广播给 sink#3

Flink Exactly-once 处理语义

Flink 框架的其中一个特点就是支持 Exactly-once 处理语义

关于处理语义,常见的有三种

  • at most once : 至多一次,不管处理结果是否成功,数据最多只会被处理一次,可能会出现漏处理
  • at least once :至少一次,数据至少会被处理一次,可能会出现重复处理
  • exactly once : 精确一次,数据正好处理一次,并且不重不漏

Flink Exactly-once 处理语义作用范围

image.png 如图是一个简化的 Flink 数据处理模型,source 算子从外部数据源摄取数据,交由 operator 处理,sink 算子将处理结果存储到外部存储系统。

Flink 只能保证中间 operator 算子的 Exactly-once 处理语义,头(source 算子) 和尾(sink 算子)的 Exactly-once 处理语义,由于涉及到外部系统,Flink 无法完全掌控,需要外部系统的支持,才能实现Exactly-once 处理语义

source 算子要想做到 Exactly-once 处理语义,需要数据源支持数据重放,保证即使发生故障,导致处理中的数据丢失,也可以通过数据重放找回来。支持数据重放的数据源,最常见的为 kafka

image.png

Flink Exactly-once 处理语义的实现原理

Flink 的 Exactly-once 处理语义是通过 checkpoint 机制实现的。详细请参考 Flink Checkpoint 原理 这一小节

Checkpoint 的使用

开启 checkpoint

默认情况下,checkpoint 功能是禁用 ,需要手动开启并设置 checkpoint 时间间隔,单位毫秒

 streamEnv.enableCheckpointing(1000*60);

如上,每 60 秒 job manager 进行一次 checkpoint

checkpoint 模式

checkpoint 支持两种模式(处理语义),Exactly-once 和 At-least-once,可通过配置选择

env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
  • Exactly-once : 适合大部分场景,但是处理延时会比 At-least-once 大
  • At-least-once : 适合某些延迟超低的场景,但是数据可能会被重复处理

checkpoint 超时时间

超时时间规定了算子每次 checkpoint 时的最长耗时(单位毫秒),如果超过阈值,则按超时处理,默认超时时间为 10 分钟

streamEnv.getCheckpointConfig.setCheckpointTimeout(1000*60*2);

checkpoint 最小时间间隔

设置完 checkpoint 时间间隔后,job manager 每隔一段时间会发起一次 checkpoint ,但如果设置的时间间隔太小或者 checkpoint 执行时间过长,可能会出现某些算子第一次 checkpoint 还没结束,第二次 checkpoint 又开始了,为了避免这个问题,需要设置 checkpoint 最小时间间隔,保证两个 checkpoint 之间,最少要间隔一段时间

streamEnv.getCheckpointConfig.setMinPauseBetweenCheckpoints(1000*60*5);

checkpoint 最大并行数

假设我们的任务有三个算子,有这样一种情况,第一个算子已经 checkpoint1 完了,第二个算子还在 checkpoint1 中,过了一段时间,第一个算子已经开始 checkpoint2 了,第二个算子终于 checkpoint1 完了,但是第三个算子还在 checkpoint1 中,此时就有两个算子同时在 checkpoint

checkpoint 最大并行数可以限制整个计算流程中,最多有多少个算子同时在 checkpoint ,如果不允许并行 checkpoint ,可以将最大并行数设置为1

streamEnv.getCheckpointConfig.setMaxConcurrentCheckpoints(1)

将 checkpoint 最大并行数设置为1后, checkpoint 最小时间间隔将失去意义

CheckPoint 多版本与恢复

默认情况下, Flink 只会保留最近 1 个 Checkpoint,,如果希望保留多个 Checkpoint,需要在 Flink 的配置文件 conf/flink-conf.yaml 中,添加如下配置

state.checkpoints.num-retained : 要保留的 Checkpoint 个数

如果恢复到某个 Checkpoint ,只需要指定对应的某个 Checkpoint 路径即可,如

bin/flink run -m yarn-cluster -yn 2 -yjm 1024 -ytm 1024 -s hdfs://master:9000/flink/checkpoints/467e17d2cc343e6c56255d222bae3421/chk-56/_metadata -c com.Streaming.SocketWindowWordCountCheckPoint flink-1.0-SNAPSHOT-jar-with-dependencies.jar --port 9002

SavePoint

SavePoint 介绍

SavePoint 是手动对状态进行快照,通常用于任务版本升级时,保存当前状态,以便任务版本升级后,继续从升级前的那个状态点开始执行计算

  • 全局,一致性快照。可以保存数据源 offset,operator 操作状态等信息
  • 可以从应用在过去任意做了 savepoint 的时刻开始继续消费

SavePoint 使用

1、在 flink-conf.yaml 中配置 Savepoint 存储位置。不是必须设置,但是设置后,后面创建指定 Job 的 Savepoint 时,可以不用在手动执行命令时指定 Savepoint 的位置

state.savepoints.dir: hdfs://namenode:9000/flink/savepoints

2、执行 savepoint 操作

直接触发

bin/flink savepoint jobId [targetDirectory] [-yid yarnAppId] //针对on yarn模式需要指定-yid参数

在 cancel 的时候触发

bin/flink cancel -s [targetDirectory] jobId [-yid yarnAppId] //针对on yarn模式需要指定-yid参数

3、从指定的 savepoint 启动 job

bin/flink run -s savepointPath [runArgs]

CheckPoint vs SavePoint

CheckPoint

  • 应用定时触发,用于保存状态,会过期
  • 内部应用失败重启的时候使用

SavePoint

  • 用户手动执行,是指向 Checkpoint 的指针,不会过期
  • 在升级的情况下使用

注意:为了能够在不同的作业版本之间以及不同的 Flink 版本之间顺利升级,强烈推荐程序员通过 uid (String) 方法手动的给算子赋予 ID,这些 ID 将用于确定每一个算子的状态范围。如果不手动给各算子指定 ID,则会由 Flink 自动给每个算子生成一个 ID而这些自动生成的 ID 依赖于程序的结构,并且对代码的更改是很敏感的。因此,强烈建议用户手动的设置 ID。