一、Flink 中的状态
1.1 概述
Flink中算子任务可以分为有状态和无状态。
无状态的算子任务只需要观察每个独立事件,根据当前输入的数据直接转换输出结果。比如map、flatmap
有状态的算子,除了当前数据之外,还需要一些其他的数据来得到计算结果。这些其他数据,就是所谓的状态。比如聚合算子、窗口算子都属于有状态算子
1.2 状态分类
1)托管状态
由Flink 统一管,不需要我们管。比如存钱到银行,我们只负责存钱,不管银行用这笔钱干删么。托管状态又分为算子状态和按键分区状态
①:算子状态 指 每个算子,可能有多个并行度,每个并行度维护自己的状态
②:按键分区状态 指 根据输入流中的key来维护和访问。一个子任务中按照key进行更细维护
2)原始状态
是自定义的,相当于就是开辟了一块内存,需要我们自己管理,实现状态的序列化和故障恢复
二、按键分区状态
1)值状态
状态中只保存一个"值"。ValueState<T>本身是一个接口,在open方法中初始化值。如果要用外部变量去存,为了达成按键分区的目的,需要维护HashMap,key为key
案例需求:检测每种传感器的水位值,如果连续的两个水位值超过10,就输出报警
public class KeyedValueStateDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
SingleOutputStreamOperator<WaterSensor> sensorDS = env
.socketTextStream("hadoop102", 7777)
.map(new WaterSensorMapFunction())
.assignTimestampsAndWatermarks(
WatermarkStrategy
.<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
.withTimestampAssigner((element, ts) -> element.getTs() * 1000L)
);
sensorDS.keyBy(r -> r.getId())
.process(
new KeyedProcessFunction<String, WaterSensor, String>() {
// TODO 1.定义状态
ValueState<Integer> lastVcState;
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
// TODO 2.在open方法中,初始化状态
// 状态描述器两个参数:第一个参数,起个名字,不重复;第二个参数,存储的类型
lastVcState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("lastVcState", Types.INT));
}
@Override
public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
// lastVcState.value(); // 取出 本组 值状态 的数据
// lastVcState.update(); // 更新 本组 值状态 的数据
// lastVcState.clear(); // 清除 本组 值状态 的数据
// 1. 取出上一条数据的水位值(Integer默认值是null,判断)
int lastVc = lastVcState.value() == null ? 0 : lastVcState.value();
// 2. 求差值的绝对值,判断是否超过10
Integer vc = value.getVc();
if (Math.abs(vc - lastVc) > 10) {
out.collect("传感器=" + value.getId() + "==>当前水位值=" + vc + ",与上一条水位值=" + lastVc + ",相差超过10!!!!");
}
// 3. 更新状态里的水位值
lastVcState.update(vc);
}
}
)
.print();
env.execute();
}
}
2)列表状态
将需要保存的数据,以列表的形式组织起来。ListState<T>,使用方式和一般的List非常相似
案例:针对每种传感器输出最高的3个水位值
public class KeyedListStateDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
SingleOutputStreamOperator<WaterSensor> sensorDS = env
.socketTextStream("hadoop102", 7777)
.map(new WaterSensorMapFunction())
.assignTimestampsAndWatermarks(
WatermarkStrategy
.<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
.withTimestampAssigner((element, ts) -> element.getTs() * 1000L)
);
sensorDS.keyBy(r -> r.getId())
.process(
new KeyedProcessFunction<String, WaterSensor, String>() {
ListState<Integer> vcListState;
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
vcListState = getRuntimeContext().getListState(new ListStateDescriptor<Integer>("vcListState", Types.INT));
}
@Override
public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
// 1.来一条,存到list状态里
vcListState.add(value.getVc());
// 2.从list状态拿出来(Iterable), 拷贝到一个List中,排序, 只留3个最大的
Iterable<Integer> vcListIt = vcListState.get();
// 2.1 拷贝到List中
List<Integer> vcList = new ArrayList<>();
for (Integer vc : vcListIt) {
vcList.add(vc);
}
// 2.2 对List进行降序排序
vcList.sort((o1, o2) -> o2 - o1);
// 2.3 只保留最大的3个(list中的个数一定是连续变大,一超过3就立即清理即可)
if (vcList.size() > 3) {
// 将最后一个元素清除(第4个)
vcList.remove(3);
}
out.collect("传感器id为" + value.getId() + ",最大的3个水位值=" + vcList.toString());
// 3.更新list状态
vcListState.update(vcList);
// vcListState.get(); //取出 list状态 本组的数据,是一个Iterable
// vcListState.add(); // 向 list状态 本组 添加一个元素
// vcListState.addAll(); // 向 list状态 本组 添加多个元素
// vcListState.update(); // 更新 list状态 本组数据(覆盖)
// vcListState.clear(); // 清空List状态 本组数据
}
}
)
.print();
env.execute();
}
}
3)map状态
类似于HashMap操作
4)规约状态
5)聚合状态
6)状态生存时间TTL
状态的失效其实不需要立即删除,而是可以给状态附加一个属性,也就是状态的“失效时间”。状态创建的时候,设置失效时间=当前时间+TTL。之后如果有对状态的访问和修改,可以对失效时间进行更新
三、算子状态
1)列表状态
对于一个子任务,有一个列表,而按键分区中,一个子任务按照key分区,可能是多个列表
轮询均分给新的 并行子任务
2) 联合列表状态
原先的多个子任务的状态,合并成一份完整的。会把完整列表给新的子任务,每人一份完整的
3)广播状态
所有的并行子任务都保持一份全局状态,都访问一个,就像被广播一样
数据流只能读取广播状态,不能修改。只有广播流才能修改
四、状态后端
主要负责管理本地状态的存储方式和位置
1. 分类
1)哈希状态后端
是把状态放到内存中。在内部直接把状态当做对象,保存在TaskManager的JVM堆上。底层是一个哈希Map
2)RocksDB状态后端
kv型数据库,可以把数据持久化存储到本地磁盘,默认存储在TaskManager的本地数据目录中。且RocksDB的状态数据被存储为序列化的字节数组,读写操作需要序列化和反序列化
2. 如何选择状态后端
哈希状态后端是将状态放入内存,速度比较快,但是缺点是用TaskManager的内存,状态大小受集群可用内存的限制
RocksDB是硬盘存储,且需要序列化反序列化,性能较差