要实现有且只有一次或者至少一次处理语义,需要保存相关的中间状态数据,在故障恢复时进行还原故障前系统的运行状态。在flink中,定义了操作状态和分组状态两种状态,且定义了检查点机制来定时触发检查点,触发检查点会将flink状态保存到statebackends中,所谓statebackends就是定义触发检查点后,将状态数据保存到哪里,默认是保存到jobmanager的内存中。
上述所有的状态数据都可以通过clear()方法进行清空。
keyed state,可以理解成一个map,键为keyBy的值,由flink管理,值可以是上述分类中的任意一种
/**
* 1. 需要继承RichFlatMapFunction而不是MapFunction,因为需要中间状态初始化
*/
public class CountWindowAverage extends RichFlatMapFunction<Tuple2<Long, Long>, Tuple2<Long, Long>> {
/**
* 定义成员变量为中间状态,可以是ValueState、ListState等,注意需要使用transient修饰
*/
private transient ValueState<Tuple2<Long, Long>> sum;
@Override
public void flatMap(Tuple2<Long, Long> input, Collector<Tuple2<Long, Long>> out) throws Exception {
//获取中间状态的值
Tuple2<Long, Long> currentSum = sum.value();
//业务逻辑处理
currentSum.f0 += 1;
currentSum.f1 += input.f1;
//更新中间状态的值
sum.update(currentSum);
if (currentSum.f0 >= 2) {
out.collect(new Tuple2<>(input.f0, currentSum.f1 / currentSum.f0));
//清空中间状态的值
sum.clear();
}
}
/**
* 中间状态初始化
* @param config
*/
@Override
public void open(Configuration config) {
//定义中间中台描述,
ValueStateDescriptor<Tuple2<Long, Long>> descriptor =
new ValueStateDescriptor<>(
"average", // 状态名称,需要在flink中唯一
TypeInformation.of(new TypeHint<Tuple2<Long, Long>>() {})); // 状态的类型
//可以理解成在flink中,有一个全局map,键为average,值为一个局部map,局部map的key为keyby的值,局部map的值为Tuple2
//Map>> map,
sum = getRuntimeContext().getState(descriptor);
}
}
为了使用操作状态,可以通过实现如下接口:
//每次检查点执行时,都要调用这个方法
void snapshotState(FunctionSnapshotContext context) throws Exception;
//系统初始化时或者进行故障恢复时,都要调用这个方法
void initializeState(FunctionInitializationContext context) throws Exception;
代码示例:
//实现CheckpointedFunction接口,并重写snapshotState和initializeState方法
public class BufferingSink
implements SinkFunction<Tuple2<String, Integer>>,
CheckpointedFunction {
private final int threshold;
//operator state类型的中间状态
private transient ListState<Tuple2<String, Integer>> checkpointedState;
private List<Tuple2<String, Integer>> bufferedElements;
public BufferingSink(int threshold) {
this.threshold = threshold;
this.bufferedElements = new ArrayList<>();
}
//当数据到来时,先加入集合中,如果数据条数达到一个阈值时,将集合中的数据保存到sink中,并清空集合
//类似countwindow的功能
@Override
public void invoke(Tuple2<String, Integer> value, Context contex) throws Exception {
bufferedElements.add(value);
if (bufferedElements.size() == threshold) {
for (Tuple2<String, Integer> element: bufferedElements) {
// send it to the sink
}
bufferedElements.clear();
}
}
//每次检查点执行时,都要调用这个方法
@Override
public void snapshotState(FunctionSnapshotContext context) throws Exception {
//将中间状态数据清空
checkpointedState.clear();
//从集合中添加数据到中间状态,这个方法执行完后,会将中间状态数据保存到State Backends
for (Tuple2<String, Integer> element : bufferedElements) {
checkpointedState.add(element);
}
}
//系统初始化时或者进行故障恢复时,都要调用这个方法
@Override
public void initializeState(FunctionInitializationContext context) throws Exception {
//operator state中间状态描述器
ListStateDescriptor<Tuple2<String, Integer>> descriptor =
new ListStateDescriptor<>(
"buffered-elements",
TypeInformation.of(new TypeHint<Tuple2<String, Integer>>() {}));
//初始化operator state中间状态
checkpointedState = context.getOperatorStateStore().getListState(descriptor);
//如果是故障恢复时,需要将中间状态的数据初始化到集合中
if (context.isRestored()) {
for (Tuple2<String, Integer> element : checkpointedState.get()) {
bufferedElements.add(element);
}
}
}
}
可以参考kafka的source,也是基于CheckpointedFunction实现的
没太看懂
就是将程序运行过程中的状态数据以快照的形式周期性的保存起来(后一次状态数据会覆盖前一次的状态数据),用于程序重启时恢复相关中间状态,每一次检查点的执行都会触发状态数据的保存。
检查点数据保存到哪里呢?默认保存到job manager的内存中,也可以在集群中进行配置,可以保存到文件系统中、HDFS中。
检查点执行流程:
注意:
中间状态数据只有在开启检查点后才能在检查点触发时,将中间状态数据保存到statebackends中,如果没有开启检查点,那么中间状态数据只存储在taskmanager的jvm内存中,和一般的成员属性类似,不能用于故障恢复
public class Test{
public static void main(String[] args){
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//设置statebackends为HDFS
env.setStateBackend(new FsStateBackend("hdfs://namenode:40010/flink/checkpoints"));
// 开启检查点,并没秒执行一次
env.enableCheckpointing(1000);
// 设置精确一次模式,也可以设置最少一次模式
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
//设置检查点之间最短时间间隔
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
//检查点操作必须在多少毫秒内完成,否则这次的检查点将被丢弃
env.getCheckpointConfig().setCheckpointTimeout(60000);
// 设置同时允许运行多少个检查点操作,设置setMinPauseBetweenCheckpoints后不要再设置此项
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
// 设置系统重启,是否删除检查点数据,如果设置DELETE_ON_CANCELLATION,则系统重启后,检查点数据将被删除,
// 如果设置RETAIN_ON_CANCELLATION,则系统重启后,会读取检查点的数据
env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
//故障恢复时,是否从最新的检查点进行恢复数据
env.getCheckpointConfig().setPreferCheckpointForRecovery(true);
}
}
更多检查点相关的配置可以配置在flink集群的conf/flink-conf.yaml配置文件中
statebackends是检查点存储中间状态数据的地方,一般保存如下两种类型:
在conf/flink-conf.yaml配置文件中配置statebackends如下:
state.backend: filesystem
state.checkpoints.dir: hdfs://namenode:40010/flink/checkpoints