Flink Checkpoint

1,724 阅读11分钟

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

Flink Checkpoint 简介

什么是 Flink Checkpoint

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

Flink Checkpoint 能实现什么

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

Flink Checkpoint 保存了哪些状态数据

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

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

Flink Checkpoint 原理

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

checkpoint barrier

checkpoint barrierFlink 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 idsource#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#1sink#1 将自身的 operator id 和状态数据填充到 checkpoint c1 快照表后,checkpoint c1 快照就算补充完整了,最后 flink 会将 checkpoint c1 快照存储到状态后端,一个完整的 checkpoint 流程就算结束了。

多并行度快照过程

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

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#1source#2,从图中可以看到,上游的两个流各有一个 c1 barrier

image.pngoperator#1 算子收到 source#1c1 barrier 时,并不会立即 Checkpoint

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

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

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

image.png 继续往下流,source#2c1 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#1sink#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 存储位置。不是必须设置,但是设置后,后面创建指定 JobSavepoint 时,可以不用在手动执行命令时指定 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。