FLink-13-Flink 状态State相关概念
Flink状态State的相关概念
在稍复杂的流式计算逻辑中,基本都需要记录和利用一些历史累积信息;
例如,对整数流进行全局累计求和的逻辑:
- 1 我们在算子中定义一个变量来记录累计到当前的总和;
- 2 在一条新数据到达时,就将新数据累加到总和变量中;
- 3 然后输出结果;
由上可知:状态就是用户在程序逻辑中用于记录信息的变量 (当然,依据不同的需求,状态数据可多可少,可简单可复杂)!
如上所述,state 只不过是用户编程时自定义的“变量”,跟 flink 又有何关系呢? 因为状态(状态中记录的数据),需要容错!!!程序一旦在运行中突然失败,则用户自定义的状态 所记录的数据会丢失,因而无法实现失败后重启的接续!
1.Row State和 Flink State
- Row State(自管理状态):自己定义、自己管理的状态,这种状态叫做自管理状态,自管理状态的持久化和容错都很困难、
- Flink State(托管状态):需要Flink来帮忙管理状态数据,不需要自己定义一个变量,而是要从flink的api中去获取一个状态管理器,用这个状态管理器来进行数据的增删改查等操作。
-
使用Flink state的话,对数据进行容错,必须要开启 快照机制checkpoint(持久化)
-
快照机制,每次只存储一个最新的状态数据,且会覆盖之前的状态数据,之前的状态数据都会被删除掉。
-
flink中task失败的时候,会有自动重启机制,即 failover机制(失败切换);跟job失败是两种维度,job失败是程序运行有Bug导致;开启failover机制的相关代码如下:
- 如果使用固定重启策略的话,不管哪个task,总共出现三次异常的话,就会直接抛出异常,程序停止运行。
-
在task失败后,task自动重启时,会帮用户自动加载最近一次的快照状态,而job失败重启后,不会自动加载失败之前的快照数据,需要我们手动指定加载最近一次的快照状态的位置。
-
2.算子状态(Operator State)和 键控状态(Keyed State)
- flink中state分为Operator State 和 Keyed State
- 使用operator state ,需要让用户自己写的Function类去实现CheckpointedFunction算子,然后在其中的方法initializeState中,去拿到Operator State存储器。
- 使用keyed state的时候,需要使用Rich类型的方法,这样的话,就不需要实现类似Operator state中的方法了,并且实现了Rich方法的话,可以调用open()方法。
- 并且使用keyed state的时候,每个key维护一个自己状态空间,且不同key之间状态空间不一致,如下图所示:上部分是键控状态,abcd各有一个自己的状态空间;下部分是算子状态,数据之间使用rebalance策略来轮询写出数据到下游。
- 相关代码案例如下:
3.Flink的job宕机重启时,算子状态和键控状态对应的快照数据重分配问题
1.算子状态Operator State重分配
- 如果使用算子状态Operator State的话,一般都是在source端使用,用来记录偏移量,记录偏移量的话,就不会出现关于数据逻辑的问题,只会将偏移量分配给不同分区,而不进行数据相关信息的传递,如果是在下面的算子中使用算子状态的话,会有数据参与到算子状态中记录,重启会出现数据逻辑的混乱问题;
- 综上:算子状态建议在source端使用,不建议在算子内部使用。
- 1.程序正常运行情况下的状态数据
- 2.flink的job宕机重启后,并行度可能会发生变化,可能会增加或者减少,然后对应的状态数据需要重新分配,如下图所示
- 1.程序正常运行情况下的状态数据
2.键控状态Keyed State重分配
- Keyed State不存在算子状态中数据逻辑这样的问题,这中间不会出现数据参与运算,只会使用关于keyed中记录的状态信息,但是也会出现状态重分配的情况。
- 使用keyed State时,接收数据时,数据的分发规律 和 加载以前checkpoint数据时的数据分配规律是一致的,所以重新分配不会造成任何逻辑影响。
3.Flink的数据结构状态管理器API
-
1.Flink的数据结构状态管理器包括:
- 单值状态
- list状态
- map状态
- reducing状态
- aggregate状态
-
2.相关代码案例如下:
package com.yang.flink.state;
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.ReduceFunction;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.functions.RuntimeContext;
import org.apache.flink.api.common.restartstrategy.RestartStrategies;
import org.apache.flink.api.common.state.*;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
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.StreamExecutionEnvironment;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* 键控状态使用演示
*/
public class StateKeyedStateDemo {
public static void main(String[] args) {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
env.setRuntimeMode(RuntimeExecutionMode.STREAMING);
// 开启状态数据的checkpoint机制(快照的周期,快照的模式)
env.enableCheckpointing(1000, CheckpointingMode.EXACTLY_ONCE);
// 开启快照后,就需要指定快照数据的持久化存储位置
env.getCheckpointConfig().setCheckpointStorage("file:///d:/checkpoint/");
// 开启 task级别故障自动 failover
env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3,1000));
DataStreamSource<String> source = env.socketTextStream("localhost", 9999);
// 需要使用map算子来达到一个效果:
// 每来一条数据(字符串),输出 该条字符串拼接此前到达过的所有字符串
SingleOutputStreamOperator<String> mapOperator = source.keyBy(s -> s)
.map(new RichMapFunction<String, String>() {//使用map算子时候,键控状态算子,需要实现rich的function,获取到flink的状态
// 单值状态
ValueState<String> valueState;
// list状态
ListState<String> lstState;
// map状态
MapState<String, String> mapState;
// reducing状态
ReducingState<Integer> reduceState;
// aggregate状态
AggregatingState<Integer, Double> aggState;
@Override
public void open(Configuration parameters) throws Exception {
RuntimeContext runtimeContext = getRuntimeContext();
// 获取一个 单值 结构的状态存储器
valueState = runtimeContext.getState(new ValueStateDescriptor<String>("vstate", String.class));
// 获取一个List结构的状态存储器
lstState = runtimeContext.getListState(new ListStateDescriptor<String>("lst", String.class));
// 获取一个 Map 结构的状态存储器
mapState = runtimeContext.getMapState(new MapStateDescriptor<String, String>("xx", String.class, String.class));
// 获取一个reduce聚合状态
reduceState = runtimeContext.getReducingState(new ReducingStateDescriptor<Integer>("reduceState", new ReduceFunction<Integer>() {
@Override
public Integer reduce(Integer value1, Integer value2) throws Exception {
return value1 + value2;
}
}, Integer.class));
// 获取一个aggregate聚合状态
// 比如,我们要插入整数,返回平均值
aggState = runtimeContext.getAggregatingState(new AggregatingStateDescriptor<Integer, Tuple2<Integer, Integer>, Double>("aggState", new AggregateFunction<Integer, Tuple2<Integer, Integer>, Double>() {
@Override
public Tuple2<Integer, Integer> createAccumulator() {
return Tuple2.of(0, 0);
}
@Override
public Tuple2<Integer, Integer> add(Integer value, Tuple2<Integer, Integer> accumulator) {
return Tuple2.of(accumulator.f0 + 1, accumulator.f1 + value);
}
@Override
public Double getResult(Tuple2<Integer, Integer> accumulator) {
return accumulator.f1 / (double) accumulator.f0;
}
@Override
public Tuple2<Integer, Integer> merge(Tuple2<Integer, Integer> a, Tuple2<Integer, Integer> b) {
return Tuple2.of(a.f0 + b.f0, a.f1 + a.f1);
}
}, TypeInformation.of(new TypeHint<Tuple2<Integer, Integer>>() {
})));
}
@Override
public String map(String value) throws Exception {
/**
* ValueState的数据操作api
*/
valueState.update("xxx"); // 更新掉状态中的值
String str = valueState.value(); // 获取状态中的值
/**
* listState的数据操作api
*/
Iterable<String> strings = lstState.get(); // 拿到整个liststate的数据迭代器
lstState.add("a"); // 添加一个元素到liststate中
lstState.addAll(Arrays.asList("b","c","d")); // 一次性放入多个元素到liststate中
lstState.update(Arrays.asList("1","2","3")); // 一次性将liststate中的数据替换为传入的元素
/**
* mapState的数据操作api
*/
String v = mapState.get("a"); // 从mapstate中根据一个key来获取它的value
boolean contain = mapState.contains("a"); // 判断mapstate中是否包含指定的key
Iterator<Map.Entry<String, String>> entryIterator = mapState.iterator(); // 拿到mapstate的entry迭代器
Iterable<Map.Entry<String, String>> entryIterable = mapState.entries(); // 拿到mapstate的entry的 Iterable(内含迭代器)
mapState.put("a","100"); // 往mapstate中插入一对KV
boolean isEmpty = mapState.isEmpty(); // 判断mapstate中是否没有元素(是否为空)
HashMap<String, String> dataMap = new HashMap<>();
dataMap.put("a","1");
dataMap.put("b","2");
mapState.putAll(dataMap); // 通过一个hashmap对象,来一次性放入多对KV到mapstate中
Iterable<String> keys = mapState.keys(); // 拿到mapstate中所有key
Iterable<String> values = mapState.values(); // 拿到mapstate中的所有value
mapState.remove("a"); // 从mapstate移除key=“a"的条目
/**
* reduce 状态使用
*/
reduceState.add(10); // 往聚合状态中添加数据 ,此刻状态中的数据是10
reduceState.add(20); // 往聚合状态中添加数据 ,此刻状态中的数据是30
Integer stateValue = reduceState.get(); // 从聚合状态中获取数据值
/**
* aggreate 状态使用
*/
aggState.add(10);
aggState.add(20);
Double avgDouble = aggState.get(); // 获取状态值 : 15.0
return null;
}
});
mapOperator.print();
}
}