文:王东阳
前言
在Flink中根据数据集是否根据Key进行分区,将状态分为Keyed State和Operator State(Non-keyed State)两种类型 ,在之前的文章《Flink中基于KeyedState的计算开发方法》已经详细介绍了Keyed State的概念和用法,本文将继续介绍Operator State。
Operator State与Keyed State不同的是,Operator State只和并行的算子实例绑定,和数据元素中的key无关,每个算子实例中持有所有数据元素中的一部分状态数据。Operator State支持当算子实例并行度发生变化时自动重新分配状态数据, OperatorState目前只支持使用ListState。
Operator State与并行的操作算子实例相关联,例如在Kafka Connector中,每个Kafka消费端算子实例都对应到Kafka的一个分区中,维护Topic分区和Offsets偏移量作为算子的Operator State 在Flink中可以通过 Checkpointed-Function
或者 ListCheckpointed<T extends Serializable>
两个接口来定义操作Operator State的函数。
Operator State开发实战
本章节将通过实际的项目代码演示Operator State在两种不同计算场景下的开发方法。
在样例中将演示Operator State如何融合进入Flink 的DataStream API,让用户在开发Flink应用的时候,可以将临时数据保存在State中,从State中读取数据,在运行的时候,在运行层面上与算子、Function体系融合,自动对State进行备份Checkpoint,一旦出现异常能够从保存的State中恢复状态,实现Exactly-Once 。
通过CheckpointedFunction接口操作Operator State
CheckpointedFunction
接口定义如代码所示,需要实现两个方法,当checkpoint触发时就会调用snapshotState()
方法,当初始化自定义函数的时候会调用initializeState()
方法,其中包括第一次初始化函数和从之前的checkpoints中恢复状态数据,同时initializeState()
方法中需要包含两套逻辑,
- 一个是不同类型状态数据初始化的逻辑,
- 一个是从之前的状态中恢复数据的逻辑
@Public
public interface CheckpointedFunction {
// 每当 checkpoint 触发的时候 调用这个方法
void snapshotState(FunctionSnapshotContext var1) throws Exception;
// 每次 自定义函数初始化的时候 调用此方法初始化
void initializeState(FunctionInitializationContext var1) throws Exception;
}
在每个算子中Managed Operator State都是以List形式存储,算子和算子之间的状态数据相互独立,List存储比较适合于状态数据的重新分布,Flink目前支持对Managed Operator State两种重分布的策略,分别是Even-split Redistribution和Union Redistribution。
- Even-split Redistribution:每个算子实例中含有部分状态元素的List列表,整个状态数据是所有List列表的合集。当触发 restore/redistribution 动作时,通过将状态数据平均分配成与算子并行度 相同数量的List列表,每个task实例中有一个List,其可以为空或者含有多个元素
- Union Redistribution:每个算子实例中含有所有状态元素的List列表,当触发 restore/redistribution 动作时,每个算子都能够获取到完整的状态元素列表
实现FlatMapFunction和CheckpointedFunction
在实际项目中可以通过实现FlatMapFunction
和CheckpointedFunction
完成对输入数据中每个key的数据元素数量和算子中的元素数量的统计。如代码所示,通过在initializeState()
方法中分别创建keyedState
和operatorState
两种State,存储基于Key相关的状态值以及基于算子的状态值。
import com.google.common.collect.Lists;
import java.io.IOException;
import java.util.List;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.runtime.state.FunctionInitializationContext;
import org.apache.flink.runtime.state.FunctionSnapshotContext;
import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction;
import org.apache.flink.util.Collector;
import org.apache.log4j.Logger;
public class CheckpointCount
implements FlatMapFunction<Tuple2<Integer, Long>, Tuple3<Integer, Long, Long>>, CheckpointedFunction {
private static final Logger logger = Logger.getLogger(CheckpointCount.class);
private Long operatorCount;
private ValueState<Long> keyedState;
private ListState<Long> operatorState;
public void CheckpointCount() {
}
@Override
public void flatMap(
Tuple2<Integer, Long> integerLongTuple2, Collector<Tuple3<Integer, Long, Long>> collector