小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
Flink 的 4 大基石为:Time , Window, State , Checkpoint.之前的文章已经学习了前面 3 个内容,接下来主要学习 Checkpoint 的相关内容。Checkpoint 与 State 有密不可分的关系,下面将从 Checkpoint 与 State 的关系和区别开始,再学习 Checkpoint 的执行流程,存储介质,配置管理等。然后再学习 Flink 的自动重启机制,还有页面手动重启。
Checkpoint 与 State 的关系和区别
State:
- state 维护/存储的是
某一个operator 运行的状态/历史值。 - 存储在内存中
- state 可以被记录,在失败的情况下数据还可以还原
Checkpoint:
- 某一时刻,Flink 中
所有的Operator 的当前 State 的全局快照 - 一般存储在磁盘中
可以理解为 Checkpoint 是把 State 数据定时持久化存储了,state 其实就是 Checkpoint 所做的主要持久化备份的主要数据。
Checkpoint 执行流程
barrier 可以理解为执行 checkpoint 的信号。
-
第一步,Checkpoint Coordinator 向所有 source 节点 trigger Checkpoint。SourceOperator 接收到 barrier 之后,会暂停当前的操作(暂停的时间很短,因为后面的写快照操作时异步的),并制作 state 快照,然后将自己的快照保存在指定的介质中。
-
第二步,source 节点向下游广播 barrier,这个 barrier 就是实现 Chandy-Lamport 分布式快照算法的核心,下游的 task 只有收到所有 input 的 barrier 才 会执行相应的 Checkpoint。
-
第三步,当 task 完成 state 备份后,会将备份数据的地址(state handle) 通知给 Checkpoint coordinator。
-
第四步,下游的 sink 节点收集齐上游两个 input 的 barrier 之后,会执行本地快照,这里特地展示了 RocksDB incremental Checkpoint 的流程,首 先 RocksDB 会全量刷数据到磁盘上(红色大三角表示),然后 Flink 框架会从中选择没有上传的文件进行持久化备份(紫色小三角)。
-
同样的,sink 节点在完成自己的 Checkpoint 之后,会将 state handle 返 回通知 Coordinator。
-
最后,当 Checkpoint coordinator 收集齐所有 task 的 state handle,就认为这一次的 Checkpoint 全局完成了,向持久化存储中再备份一个 Checkpoint meta 文件。
从 source 执行 checkpiont 会短暂暂停当前操作,得出经验,checkpoint 每次触发的事件应该是秒级的,比如 1秒,3秒, 5 秒等。如果是毫秒级的操作,比如 200 ms ,有可能会影响运算。
Checkpoint 状态存储后端/存储介质
MemoryStateBackend:
- 存储方式:
- state - TaskManager 内存
- checkpoint - JobManager 内存
- 容量限制:
- 单个 State maxStateSize 默认 5M
- maxStateSize <= akka.framesize 默认 10M
- 总大小不超过 JobManager 的内存
只适用于本地测试,
不适合生产使用。
FsStateBackend:
- 存储方式:
- state - TaskManager 内存
- checkpoint - 外部文件系统(本地或者 HDFS)
- 容量限制:
- 单 TaskManager 上的 State 总量不超过它的内存
- 总大小不超过配置的文件系统容量
常规使用状态的作业,例如分钟级别窗口聚合/join;需要开启 HA 的作业。可以在生产场景使用。
所以通常使用 FsStateBackend 来做存储介质。
RocksDBStateBackend:
- 存储方式:
- state - TaskManager 上的 KV 数据库(实际使用内存 + 磁盘)
- checkpoint - 外部文件系统(本地或者 HDFS)
- 容量限制:
- 单 TaskManager 上的 State 总量不超过它的内存 + 磁盘
- 单个 Key 最大 2 G
- 总大小不超过配置的文件系统容量
适合超大状态作业的场景,对状态读写性能要求不高的作业。可以在生产场景使用。
使用时需要在程序中添加依赖。
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-statebackend-rocksdb_2.11</artifactId>
<version>1.13.2</version>
</dependency>
新版本后,发生了一些更改:
配置说明
配置可以在官网中找到:
package com.learn.flink.checkpoint;
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.runtime.state.filesystem.FsStateBackend;
import org.apache.flink.streaming.api.CheckpointingMode;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.CheckpointConfig;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
/**
* 演示 Flink - checkpoint 相关配置
*/
public class CheckpointDemo1 {
public static void main(String[] args) throws Exception {
//0:env
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
//1: source
// ################## 类型一: 必须参数 ###############
// 设置 checkpoint 的时间间隔为 1000 毫秒/其实就是每隔 1000 ms 发一次 barrier
env.enableCheckpointing(1000);
// 设置存储位置
env.setStateBackend(new FsStateBackend("file:///D:\workspace\TEST\learn-flink\data\ckp"));
// env.getCheckpointConfig().setCheckpointStorage("hdfs://192.168.98.101:9000/flink/flink-checkpoints");
// ################## 类型二: 建议参数 ###############
// 设置两个 checkpoint 之间的最小等待时间(为了避免每个 1000 ms 做一次 checkpoint 的时候,前一次太慢和后一个重叠在一起)
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
// 设置容忍检查点失败的次数,默认是 0 ,即不容忍,只要有一个 checkpoint 失败,就认为任务失败
env.getCheckpointConfig().setTolerableCheckpointFailureNumber(2);
// CheckpointConfig.ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION:当外部作用被取消时,删除外部 checkpoint(默认值)
// CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION:当外部作用被取消时,保留外部 checkpoint
env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
// ################## 类型三: 直接使用默认配置即可 ###############
// checkpoint 执行模式 (默认为 EXACTLY_ONCE)
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 设置 checkpoint 超时时间,如果 60 s 内没有完成,则认为该次 checkpoint 失败,则丢弃
env.getCheckpointConfig().setCheckpointTimeout(60000);
//设置一次有多少个 checkpoint 可以同时执行,默认为 1
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
final DataStreamSource<String> ds = env.socketTextStream("node01", 999);
//2: transformation
//3: sink
ds.print();
//4: execute
env.execute();
}
}
可以看到一些配置也有些变化。
状态恢复和重启策略
自动重启
- 默认重启策略:这个是无限重启的,可以解决小问题,但是可能会隐藏真正的问题。
- 无重启策略
- 固定延迟重启策略:开发中使用
- 失败率重启策略:开发中偶而使用
模拟异常的例子:
package com.learn.flink.checkpoint;
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.restartstrategy.RestartStrategies;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.runtime.state.filesystem.FsStateBackend;
import org.apache.flink.streaming.api.CheckpointingMode;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.CheckpointConfig;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
import java.util.concurrent.TimeUnit;
/**
* 演示 Flink - checkpoint 相关配置
*/
public class CheckpointDemo2_restart {
public static void main(String[] args) throws Exception {
//0:env
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
//1: source
// ################## 类型一: 必须参数 ###############
// 设置 checkpoint 的时间间隔为 1000 毫秒/其实就是每隔 1000 ms 发一次 barrier
env.enableCheckpointing(1000);
// 设置存储位置
env.setStateBackend(new FsStateBackend("file:///D:\workspace\TEST\learn-flink\data\ckp"));
// env.getCheckpointConfig().setCheckpointStorage("hdfs://192.168.98.101:9000/flink/flink-checkpoints");
// ################## 类型二: 建议参数 ###############
// 设置两个 checkpoint 之间的最小等待时间(为了避免每个 1000 ms 做一次 checkpoint 的时候,前一次太慢和后一个重叠在一起)
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
// 设置容忍检查点失败的次数,默认是 0 ,即不容忍,只要有一个 checkpoint 失败,就认为任务失败
env.getCheckpointConfig().setTolerableCheckpointFailureNumber(2);
// CheckpointConfig.ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION:当外部作用被取消时,删除外部 checkpoint(默认值)
// CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION:当外部作用被取消时,保留外部 checkpoint
env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
// ################## 类型三: 直接使用默认配置即可 ###############
// checkpoint 执行模式 (默认为 EXACTLY_ONCE)
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 设置 checkpoint 超时时间,如果 60 s 内没有完成,则认为该次 checkpoint 失败,则丢弃
env.getCheckpointConfig().setCheckpointTimeout(60000);
//设置一次有多少个 checkpoint 可以同时执行,默认为 1
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
//默认重启策略:这个是无限重启的,可以解决小问题,但是可能会隐藏真正的问题。
//如果设置为无重启策略,则抛出异常后,程序停止
//env.setRestartStrategy(RestartStrategies.noRestart());
//固定延迟重启,最多重启 3 次,间隔为 5 秒---开发中常使用
// env.setRestartStrategy(RestartStrategies.fixedDelayRestart(
// 3, //最多重启 3 次
// Time.of(5, TimeUnit.SECONDS))// 重启的时间间隔
// );
// 失败率重启:开发中偶而使用
env.setRestartStrategy(RestartStrategies.failureRateRestart(
3, // 每个测量阶段内最大失败次数
Time.of(5, TimeUnit.SECONDS),// 失败率测量的时间间隔
Time.of(10, TimeUnit.SECONDS)// 两次连续重启的时间间隔
));
final DataStreamSource<String> linesDS = env.socketTextStream("node01", 999);
//2: transformation
// 切割并记录为 1
final SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOne = linesDS.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> collector) throws Exception {
final String[] arr = value.split(" ");
for (String word : arr) {
if("bug".equalsIgnoreCase(word)) {
System.out.println("bug.........");
throw new Exception("bug...");
}
collector.collect(Tuple2.of(word, 1));
}
}
});
// 分组
final KeyedStream<Tuple2<String, Integer>, String> grouped = wordAndOne.keyBy(t -> t.f0);
//聚合
final SingleOutputStreamOperator<Tuple2<String, Integer>> result = grouped.sum(1);
//3: sink
result.print();
//4: execute
env.execute();
}
}
在之前的代码中添加异常信息:
- 默认无重启策略:当输入 bug 单词的时候,会抛出异常并打印,然后继续输入单词,会从历史记录的计数开始继续累加。
说明在配置 checkpoint 后,Flink 包含默认重启策略,并且可以一直无限重启。那样这种默认重启策略可以解决小问题,但是可能会隐藏真正的问题。
注意重启策略需要配置在 source 之前,并且只有在配置了 checkpoint 后才是有效的
- 设置无重启策略,增加配置
env.setRestartStrategy(RestartStrategies.noRestart());
则结果为抛出异常,任务停止,没有再重启。
- 设置固定延迟重启策略,增加配置
env.setRestartStrategy(RestartStrategies.fixedDelayRestart(
3, //最多重启 3 次
Time.of(5, TimeUnit.SECONDS))// 重启的时间间隔
);
可以设置重启的最大次数,并设置间隔时间。每次重启会间隔配置的时间,重启次数到达最大值后,不再重启,任务停止。这种方式在开发中是经常使用的,比如遇到一些网络的问题,通过这种方式可以很好的处理。
- 设置失败率重启策略,增加配置
env.setRestartStrategy(RestartStrategies.failureRateRestart(
3, // 每个测量阶段内最大失败次数
Time.of(5, TimeUnit.SECONDS),// 失败率测量的时间间隔
Time.of(10, TimeUnit.SECONDS)// 两次连续重启的时间间隔
));
可以设置在每个测量阶段内的最大失败次数,并设置失败率测量的时间间隔,并设置两次重启的时间间隔。这种方式在开发种偶而使用。有了时间间隔不好控制时间,不常使用。
手动重启
手动重启其实就是使用 Flink 的 web 页面,上传任务,然后重启任务,在重启的时候可以指定checkpoint进行恢复。
示例:单词计数,输出到 kafka 种
package com.example.flink;
import org.apache.commons.lang3.SystemUtils;
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.runtime.state.filesystem.FsStateBackend;
import org.apache.flink.streaming.api.CheckpointingMode;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.CheckpointConfig;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer;
import org.apache.flink.util.Collector;
import java.util.Properties;
public class WordCountDemo3 {
public static void main(String[] args) throws Exception {
// TODO 0: env
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
// ################## 类型一: 必须参数 ###############
// 设置 checkpoint 的时间间隔为 1000 毫秒/其实就是每隔 1000 ms 发一次 barrier
env.enableCheckpointing(1000);
// 设置存储位置
if (SystemUtils.IS_OS_WINDOWS) {
env.setStateBackend(new FsStateBackend("file:///D:\workspace\TEST\learn-flink\data\ckp"));
} else {
env.setStateBackend(new FsStateBackend("hdfs://node01:9000/flink/checkpoints-test"));
}
// ################## 类型二: 建议参数 ###############
// 设置两个 checkpoint 之间的最小等待时间(为了避免每个 1000 ms 做一次 checkpoint 的时候,前一次太慢和后一个重叠在一起)
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
// 设置容忍检查点失败的次数,默认是 0 ,即不容忍,只要有一个 checkpoint 失败,就认为任务失败
env.getCheckpointConfig().setTolerableCheckpointFailureNumber(2);
// CheckpointConfig.ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION:当外部作用被取消时,删除外部 checkpoint(默认值)
// CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION:当外部作用被取消时,保留外部 checkpoint
env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
// ################## 类型三: 直接使用默认配置即可 ###############
// checkpoint 执行模式 (默认为 EXACTLY_ONCE)
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 设置 checkpoint 超时时间,如果 60 s 内没有完成,则认为该次 checkpoint 失败,则丢弃
env.getCheckpointConfig().setCheckpointTimeout(60000);
//设置一次有多少个 checkpoint 可以同时执行,默认为 1
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
// TODO 1: source
DataStream<String> text = env.socketTextStream("node01", 9999);
// TODO 2: transformation
DataStream<String> counts = text
.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out) throws Exception {
// normalize and split the line
String[] tokens = value.toLowerCase().split(" ");
// emit the pairs
for (String token : tokens) {
if (token.length() > 0) {
out.collect(new Tuple2<>(token, 1));
}
}
}
})
.keyBy(value -> value.f0)
.sum(1)
.map(new MapFunction<Tuple2<String, Integer>, String>() {
@Override
public String map(Tuple2<String, Integer> value) throws Exception {
return value.f0 + ":" + value.f1;
}
});
// TODO 3: sink
Properties propsProducer = new Properties();
propsProducer.setProperty("bootstrap.servers", "node01:9092");
final FlinkKafkaProducer<String> kafkaSink = new FlinkKafkaProducer<>("flink_kafka2", new SimpleStringSchema(), propsProducer);
counts.addSink(kafkaSink);
// TODO 4: execute
env.execute("WordCountDemo3");
}
}
使用 WEB 页面提交的任务时遇到一个坑,就是上传的 jar 包总是报错:
WARNING: All illegal access operations will be denied in a future release
java.lang.NoClassDefFoundError: org/apache/flink/streaming/connectors/kafka/FlinkKafkaProducer
at com.example.flink.WordCountDemo3.main(WordCountDemo3.java:51)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.apache.flink.client.program.PackagedProgram.callMainMethod(PackagedProgram.java:355)
at org.apache.flink.client.program.PackagedProgram.invokeInteractiveModeForExecution(PackagedProgram.java:222)
at org.apache.flink.client.ClientUtils.executeProgram(ClientUtils.java:114)
at org.apache.flink.client.cli.CliFrontend.executeProgram(CliFrontend.java:812)
at org.apache.flink.client.cli.CliFrontend.run(CliFrontend.java:246)
at org.apache.flink.client.cli.CliFrontend.parseAndRun(CliFrontend.java:1054)
at org.apache.flink.client.cli.CliFrontend.lambda$main$10(CliFrontend.java:1132)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/javax.security.auth.Subject.doAs(Subject.java:423)
at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1754)
at org.apache.flink.runtime.security.contexts.HadoopSecurityContext.runSecured(HadoopSecurityContext.java:41)
at org.apache.flink.client.cli.CliFrontend.main(CliFrontend.java:1132)
Caused by: java.lang.ClassNotFoundException: org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588)
at org.apache.flink.util.FlinkUserCodeClassLoader.loadClassWithoutExceptionHandling(FlinkUserCodeClassLoader.java:64)
at org.apache.flink.util.ChildFirstClassLoader.loadClassWithoutExceptionHandling(ChildFirstClassLoader.java:65)
at org.apache.flink.util.FlinkUserCodeClassLoader.loadClass(FlinkUserCodeClassLoader.java:48)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 17 more
这里就是缺少 jar 包,需要把依赖的 jar 包都打入才可以。在 pom.xml 文件种增加配置:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version><!--$NO-MVN-MAN-VER$-->
<executions>
<!-- Default Execution -->
<execution>
<id>default</id>
<phase>package</phase>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
<!--WordCountDemo3-->
<execution>
<id>WordCountDemo3</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>WordCountDemo3</classifier>
<archive>
<manifestEntries>
<program-class>com.example.flink.WordCountDemo3</program-class>
</manifestEntries>
</archive>
<includes>
<include>com/example/flink/WordCountDemo3.class</include>
<include>com/example/flink/WordCountDemo3$*.class</include>
<include>META-INF/LICENSE</include>
<include>META-INF/NOTICE</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
<!--To build an application JAR that contains all dependencies required for declared connectors and libraries-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>com.google.code.findbugs:jsr305</exclude>
<exclude>org.slf4j:*</exclude>
<exclude>log4j:*</exclude>
</excludes>
</artifactSet>
<filters>
<filter>
<!-- Do not copy the signatures in the META-INF folder.
Otherwise, this might cause SecurityExceptions when using the JAR. -->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.flink.WordCountDemo3</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
添加这个配置后,可以提交成功并正常执行任务。
-
打包,注意添加上面的 pom 配置
-
通过网页上传任务
-
跳转到任务运行页面
-
手动取消任务
-
查看 HDFS 种保存的 checkpoint
-
再次提交任务,并指定 savepoint
hdfs://node01:9000/flink/checkpoints-test/993a9c7f217176fc56f3533c37647e9a/chk-81 -
任务执行后会从指定的 savepoint 的位置开始 state 的计算
从 7 的位置恢复的,再发送数据时,计数开始增加。