1.checkpoint介绍

Checkpoint,就是流式程序中用来做容错的机制。

它是通过JobManager的检查点协调器(checkpoint coordinator)来协调工作的。

2.checkpoint的执行流程

执行流程参考如下:

(1)checkpoint coordinator(检查点协调器)会周期性发送一个个的barrier(栅栏),这个barrier会随着数据流,流向source算子

(2)算子在处理时,如果发现是barrier(栅栏),那么这个算子就会停下手里的工作,把状态向checkpoint coordinator进行状态的汇报

(3)在状态汇报完之后,barrier就会随着数据流继续往下游传递

(4)下游的算子在碰到barrier后,会重复上面的流程

(5)以此类推,直到所有算子的状态都汇报完成,这一轮的checkpoint(快照)就做完了

(6)如果中间出现状态汇报失败等各种情况,说明这一轮的checkpoint制作失败。会等待下一轮的checkpoint快照制作。

3.Restart Strategy(重启策略)

Flink的重启策略有四种,分别是:

  • noRestart(不重启)
  • fixedDelayRestart(固定延迟重启)
  • failureRateRestart(失败率重启)
  • exponentialDelayRestart(指数延迟重启)

不重启

如果程序出错了,那就停止。

如果checkpoint关闭,那么默认就是不重启策略。

配置如下:

#1.配置文件中的写法
restart-strategy.type: none


#2.Java代码配置
env.setRestartStrategy(RestartStrategies.noRestart());

固定延迟重启

运行流式程序固定能够重启的次数。比如5次。

如果checkpoint开启,默认的重启的次数就是整形的最大值。(Integer.MAX_VALUE)

代码中配置如下:

#1.Java代码
env.setRestartStrategy(RestartStrategies.fixedDelayRestart(
  3, // 重启的次数
  Time.of(10, TimeUnit.SECONDS) // 每一次重启的间隔时间
));



#2.配置文件中的写法
restart-strategy.fixed-delay.attempts: 3
restart-strategy.fixed-delay.delay: 10 s


#1.上面两种的含义如下
每隔10秒钟任务重启,总的重启次数为3次。

失败率重启

在一定的时间范围内,运行任务失败的频率。比如:1分钟允许失败3次。

代码配置如下:

#1.配置文件中的写法
restart-strategy.type: failure-rate
restart-strategy.failure-rate.max-failures-per-interval: 3
restart-strategy.failure-rate.failure-rate-interval: 5 min
restart-strategy.failure-rate.delay: 10 s


#2.Java代码的写法
env.setRestartStrategy(RestartStrategies.failureRateRestart(
  3, // 最大的重启次数
  Time.of(5, TimeUnit.MINUTES), //在五分钟内
  Time.of(10, TimeUnit.SECONDS) // 每一次重启的间隔时间
));


#3.上面两个配置含义如下
在5分钟之内,最多允许重启3次。

指数延迟重启

每一次的重启会随着指数的递增而递增。

配置参考如下:

#1.配置文件中的写法
restart-strategy.type: exponential-delay
restart-strategy.exponential-delay.initial-backoff: 10 s
restart-strategy.exponential-delay.max-backoff: 2 min
restart-strategy.exponential-delay.backoff-multiplier: 2.0
restart-strategy.exponential-delay.reset-backoff-threshold: 10 min
restart-strategy.exponential-delay.jitter-factor: 0.1



#2.Java代码的写法
env.setRestartStrategy(RestartStrategies.exponentialDelayRestart(
  Time.milliseconds(1),
  Time.milliseconds(1000),
  1.1, // exponential multiplier
  Time.milliseconds(2000), // threshold duration to reset delay to its initial value
  0.1 // jitter
));


#3.上面两个配置的含义如下
任务初始的重启时间为1毫秒,最大的重启时间为1秒钟,指数为1.1
如果任务稳定运行超过2秒,那么重启时间会重置为初始值(1毫秒)
抖动因子,是为了防止大量的任务在同一时刻重启。

重启策略小结

工作中,一般使用固定延迟重启或者失败率重启。

4.StateBackend(状态后端)

状态后端,就是专门用来保存checkpoint coordinator(检查点协调器)快照数据的。

默认情况下,它保存在JobManager的内存里。很显然,这种方式不太好。

Flink提供了3种状态后端的保存方式:

  • memoryStateBackend(内存状态后端)
  • FsStateBackend(文件系统状态后端)
  • RocksDBStateBackend(RocksDB数据库状态后端)

memoryStateBackend

统一全局快照数据保存在JobManager的内存中。这种方式几乎不用。

从节点算子的状态保存在TaskManager的内存中。

配置参考如下:

#1.配置文件中
state.backend: hashmap
state.checkpoint-storage: jobmanager


#2.Java代码中
env.setStateBackend(new HashMapStateBackend());
env.getCheckpointConfig().setCheckpointStorage(new JobManagerCheckpointStorage());

FsStateBackend

统一全局快照数据保存在文件系统中,比如:HDFS。

从节点算子的状态保存在TaskManager的内存中。

整体比较安全,这也是推荐的状态后端保存方式。

配置参考如下:

#1.配置文件中
state.backend: hashmap
state.checkpoints.dir: file:///checkpoint-dir/
state.checkpoint-storage: filesystem


#2.Java代码中
env.setStateBackend(new HashMapStateBackend());
env.getCheckpointConfig().setCheckpointStorage("file:///checkpoint-dir");
env.getCheckpointConfig().setCheckpointStorage(new FileSystemCheckpointStorage("file:///checkpoint-dir"));

RocksDBStateBackend

这是一种可以保存超大状态的状态后端,也是唯一一个可以支持增量状态的保存的状态后端,一般用的不多。

RocksDB:是一个本地的数据库。这个数据库就在TaskManager。

如果是用RocksDBStateBackend,Java代码中必须添加RocksDB的依赖:

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-statebackend-rocksdb</artifactId>
    <version>1.15.4</version>
    <scope>provided</scope>
</dependency>

配置参考如下:

#1.配置文件中
state.backend: rocksdb
state.checkpoints.dir: file:///checkpoint-dir/
state.checkpoint-storage: filesystem


#2.Java代码中,注意:必须添加rocksdb的pom依赖才行
env.setStateBackend(new EmbeddedRocksDBStateBackend());
env.getCheckpointConfig().setCheckpointStorage("file:///checkpoint-dir");
env.getCheckpointConfig().setCheckpointStorage(new FileSystemCheckpointStorage("file:///checkpoint-dir"));

小结

在公司中,一般使用FsStateBackend就够了。

综合案例

package day07;

import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.restartstrategy.RestartStrategies;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.CheckpointingMode;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.CheckpointConfig;
import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

/**
 * @author: itcast
 * @date: 2023/4/13 21:00
 * @desc: 需求:演示checkpoint机制
 */
public class Demo01_CheckpointDemo {
    public static void main(String[] args) throws Exception {
        //1.构建流式执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        //开启checkpoint,并设置为1秒钟
        env.enableCheckpointing(1000);

        // 额外配置信息,最后配置
        // 设置为精准一次语义,默认就是Exactly_once
        env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
        // 设置两次checkpoint的间隔最小为500毫秒
        env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
        // 设置超时时间
        env.getCheckpointConfig().setCheckpointTimeout(60000);
        // 设置同时只能有1个checkpoint正在运行
        env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
        //任务取消后,checkpoint目录是否保存,这里配置的是保存
        env.getCheckpointConfig().setExternalizedCheckpointCleanup(
                CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
        //配置checkpoint的存储路径
        env.getCheckpointConfig().setCheckpointStorage("file:///d:\\checkpoint");


        //设置重启策略为固定延迟重启
        env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3,1000));

        //2.数据源
        DataStreamSource<String> source = env.socketTextStream("node1", 9999);

        //3.数据处理
        SingleOutputStreamOperator<Tuple2<String, Integer>> mapData = source.map(new MapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String value) throws Exception {
                if ("aaa".equals(value)) {
                    throw new Exception("敏感词来了,程序挂了……");
                }
                return Tuple2.of(value, 1);
            }
        });
        SingleOutputStreamOperator<Tuple2<String, Integer>> result = mapData.keyBy(value -> value.f0)
                .sum(1);

        //4.数据输出
        result.print();

        //5.启动流式任务
        env.execute();
    }
}