Flink状态管理
flink中的状态
- 由一个任务维护,并且用来计算某个结果的所有数据,都属于这个任务的状态。
- 可以认为状态就是一个本地变量,可以被任务的业务逻辑访问
- Flink 会进行状态管理,包括状态一致性,故障处理以及高效存储和访问,以便开发人员可以专注于应用程序的逻辑。
在flink中,状态始终与特定算子相关联
为了使运行时的flink了解算子的状态,算子需要预先注册其状态。
总的来说有两种类型的状态:
- 算子状态(Operatior State)
- 算子状态的作用范围限定为算子任务
- 键控状态(Keyed State)
- 根据输入数据流中定义的键(key)来维护和访问
算子状态(Operatior State)
- 算子状态的作用范围限定为算子任务,由同一并行任务所处理的所有数据都可以访问到相同的状态
- 状态对于同一子任务而言是共享的
- 算子状态不能由相同或不同算子的另一个子任务访问
算子状态数据结构
- 列表状态(List state)
- 将状态表示为一组数据的列表
- 联合列表状态(Union list state)
- 也就状态表示为数据的列表。与常规列表的状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复
- 广播状态(Broadcast state)
- 如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。
- 案例代码:
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
//DataStream<String> filedata = env.readTextFile("data/temps.txt");
DataStream<String> localhost = env.socketTextStream("localhost", 7777);
DataStream<TempInfo> dataStream = localhost.map(new MapFunction<String, TempInfo>() {
@Override
public TempInfo map(String value) throws Exception {
String[] split = value.split(",");
return new TempInfo(split[0],new Long(split[1]),new Double(split[2]));
}
});
// 定义一个有状态的map操作,统计当前分区数据个数
dataStream.map(new MyCounterMap());
env.execute();
}
// 自定义map function
static class MyCounterMap implements MapFunction<TempInfo,Integer>, ListCheckpointed<Integer>{
// 定义状态变量
private Integer count;
@Override
public Integer map(TempInfo value) throws Exception {
count++;
return count;
}
@Override
public List<Integer> snapshotState(long checkpointId, long timestamp) throws Exception {
return Collections.singletonList(count);
}
@Override
public void restoreState(List<Integer> state) throws Exception {
for(Integer s : state){
count += s;
}
}
}
键控状态(Keyed State)
- 键控状态是根据输入数据流中定义的键(key)来维护和访问的
- Flink为每个key维护一个状态实例,并将具有相同键的所有数据,都分区到同一个算子任务中,这个任务会维护和处理这个key对应的状态
- 当任务处理一条数据时,它会自动将状态的访问范围限定为当前数据的key
键控状态数据结构
- 值状态(Value state)
- 将状态表示为单个的值
- 列表状态(List state)
- 将状态表示为一组数据的列表
- 映射状态(Map state)
- 将状态表示为一组key-value对
- 聚合状态(Reducing state & Aggregating State)
- 将状态表示为一个用于聚合操作的列表
键控状态的使用
- 声明一个键控状态
myValueState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("my-value",Integer.class));
- 读取状态
Integer myValue = myValueState.value();
- 对状态赋值
myValueState.update(10);
- 使用测试
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
//DataStream<String> filedata = env.readTextFile("data/temps.txt");
DataStream<String> localhost = env.socketTextStream("localhost", 7777);
DataStream<TempInfo> dataStream = localhost.map(new MapFunction<String, TempInfo>() {
@Override
public TempInfo map(String value) throws Exception {
String[] split = value.split(",");
return new TempInfo(split[0],new Long(split[1]),new Double(split[2]));
}
});
dataStream.keyBy(data -> data.getId())
.map(new MyKeyCounterMapper());
env.execute();
}
static class MyKeyCounterMapper extends RichMapFunction<TempInfo,Integer>{
private ValueState<Integer> keyCountState;
private ListState<String> myListState;
private MapState<String,Double> myMapState;
private ReducingState<TempInfo> myReduceState;
@Override
public void open(Configuration parameters) throws Exception {
keyCountState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("key-count",Integer.class));
myListState = getRuntimeContext().getListState(new ListStateDescriptor<String>("my-list",String.class));
myMapState = getRuntimeContext().getMapState(new MapStateDescriptor<String, Double>("my-map",String.class,Double.class));
myReduceState = getRuntimeContext().getReducingState(new ReducingStateDescriptor<TempInfo>("my-reduce", new ReduceFunction<TempInfo>() {
@Override
public TempInfo reduce(TempInfo value1, TempInfo value2) throws Exception {
return new TempInfo();
}
}, TempInfo.class));
}
@Override
public Integer map(TempInfo value) throws Exception {
Integer count = keyCountState.value();
count++;
keyCountState.update(count);
// list操作
Iterable<String> strings = myListState.get();
// map操作
myMapState.put("test",1.2);
myMapState.get("1");
myMapState.remove("2");
// reduce 操作 传入直接进行reduce聚合
myReduceState.add(value);
return count;
}
}
- 案例(这里我们要注意在不同并行度下状态收集的触发机制,感兴趣的可以试试关闭并行度为1时代码的运行测试效果)
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStream<String> localhost = env.socketTextStream("localhost", 7777);
DataStream<TempInfo> dataStream = localhost.map(new MapFunction<String, TempInfo>() {
@Override
public TempInfo map(String value) throws Exception {
String[] split = value.split(",");
return new TempInfo(split[0],new Long(split[1]),new Double(split[2]));
}
});
// 定义flatmap操作,监控温度跳变,进行报警
dataStream.keyBy(data -> data.getId())
.flatMap(new TempChangeWarn(10.0));
env.execute();
}
static class TempChangeWarn extends RichFlatMapFunction<TempInfo, Tuple3<String,Double,Double>>{
//定义属性,温度跳变阀值
private Double threshold;
public TempChangeWarn(Double threshold){
this.threshold = threshold;
}
// 定义状态,保存上一次状态值
private ValueState<Double> lastTempState;
@Override
public void open(Configuration parameters) throws Exception {
// 初始化
lastTempState = getRuntimeContext().getState(new ValueStateDescriptor<Double>("last-temp",Double.class));
}
@Override
public void flatMap(TempInfo value, Collector<Tuple3<String, Double, Double>> out) throws Exception {
Double lastTemp = lastTempState.value();
if(lastTemp != null){
Double diff = Math.abs(value.getTemp() - lastTemp);
if(diff >= threshold){
out.collect(new Tuple3<String, Double, Double>(value.getId(),lastTemp,value.getTemp()));
}
}
// 更新状态
lastTempState.update(value.getTemp());
}
@Override
public void close() throws Exception {
lastTempState.clear();
}
}
状态后端(State Backends)
- 每传入一条数据,有状态的算子任务都会读取和更新状态
- 由于有效的状态访问对于处理数据的低延迟至关重要,因此每个并行任务都会在本地维护其状态,以确保快速的状态访问。
- 状态的存储,访问以及维护,由一个可插入的组件决定,这个组件就叫做状态后端(State Backends)
- 状态后端主要负责两件事:本地的状态管理,以及将检查点(checkpoint)状态写入远程存储
选择一个状态后端
- MemoryStateBackend
内存级的状态后端,会将键控状态作为内存中的对象进行管理,将它们存储在TaskManager的JVM堆上,而将checkpoint存储在JobManager的内存中
特点:快速,低延迟,但不稳定 - FsStateBackend
将checkpoint存到远程的持久化文件系统(FileSystem)上,而对于本地状态跟MemoryStateBackend一样,也会存在TaskManager的JVM堆上
特点:同时拥有内存级的本地访问速度,和更好的容错保证 - RocksDBStateBackend
将所有状态序列化后,存入本地的RocksDB中存储。
flink配置文件中设置:conf下flink-conf.yaml配置文件
代码中设置:
RocksDBStateBackend
将所有状态序列化后,存入本地的RocksDB中存储。
注意:RocksDB的支持并不直接包含在flink中,需要引入依赖
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-statebackend-rocksdb_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
- 代码设置
env.setStateBackend(new MemoryStateBackend());
env.setStateBackend(new FsStateBackend("hdfs route"));
env.setStateBackend(new RocksDBStateBackend("hdfs route"));