一:算子状态
算子状态:一个算子,会有多个并行子任务。作用范围被限定为当前算子任务。算子状态跟数据的key无关,所以不同key的数据只要被分发到同一个并行子任务,就会访问到同一个Operator State。
算子状态的实际应用场景不如Keyed State多,一般用在Source或Sink等与外部系统连接的算子上,或者完全没有key定义的场景。比如Flink的Kafka连接器中,就用到了算子状态。
当算子的并行度发生变化时 ,算子状态也支持在并行的算子任务实例之间做重组分配。根据状态的类型不同,重组分配的方案也会不同。
算子状态也支持不同的结构类型,主要有三种:ListState、UnionListState和BroadcastState。
二:列表状态
1:使用普通变量实现
// 需求,在map算子上,计算每一个并行度处理多少数据
public class Flink08_OpeState_Var {
public static void main(String[] args) throws Exception {
// 1. 准备环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 配置并行度
env.setParallelism(2);
// 3. 读取数据
DataStreamSource<String> socketDs = env.socketTextStream("bigdata137", 8888);
// 4. 数据类型转换
SingleOutputStreamOperator<WaterSensor> wsDs = socketDs.map(new WaterSensorMapFunction());
SingleOutputStreamOperator<String> map = wsDs.map(new RichMapFunction<WaterSensor, String>() {
Integer count = 0;
@Override
public String map(WaterSensor value) throws Exception {
// 使用复函数,可以通过复函数获取更丰富的上下文信息。
return "并行子任务"+getRuntimeContext().getIndexOfThisSubtask() + "处理了"+ ++count+"条数据";
}
});
//7. 打印输出
map.print();
//8. 提交作业
env.execute();
}
}
2:案例分析
1> 并行子任务0处理了1条数据
2> 并行子任务1处理了1条数据
1> 并行子任务0处理了2条数据
2> 并行子任务1处理了2条数据
1> 并行子任务0处理了3条数据
2> 并行子任务1处理了3条数据
1> 并行子任务0处理了4条数据
2> 并行子任务1处理了4条数据
[root@bigdata137 ~]# nc -lk 8888
ws1,1,1
ws1,1,1
ws1,1,1
ws1,1,1
ws1,1,1
ws1,1,1
ws1,1,1
ws1,1,1
基于一个integer就已经实现了需求,并且并行度之间是隔离的。
严格来讲,状态和普通变量就差别一个序列化能力。
如果三个并行度变成了两个并行度。把并行子任务上的状态数据,重新分配给扩缩之后的并行度子任务。
例如:
列表状态:上游三个算子【1,2】【3,4】【5,6】缩容两个算子【135】【246】
联合列表:上游三个算子【1,2】【3,4】【5,6】缩容两个算子【123456】【123456】
3:算子状态实现
把你的普通变量放到状态上去,用状态的能力给你持久化,和做容灾回复。
package com.dashu.day08;
import com.dashu.bean.WaterSensor;
import com.dashu.bean.WaterSensorMapFunction;
import org.apache.commons.compress.archivers.dump.DumpArchiveEntry;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
import org.apache.flink.api.common.state.OperatorStateStore;
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.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
// 需求,在map算子上,计算每一个并行度处理多少数据
public class Flink09_OpeState_State {
public static void main(String[] args) throws Exception {
// 1. 准备环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 配置并行度
env.setParallelism(2);
env.enableCheckpointing(10000);
// 3. 读取数据
DataStreamSource<String> socketDs = env.socketTextStream("bigdata137", 8888);
// 4. 数据类型转换
SingleOutputStreamOperator<WaterSensor> wsDs = socketDs.map(new WaterSensorMapFunction());
SingleOutputStreamOperator<String> map = wsDs.map(new MyMap());
//7. 打印输出
map.print();
//8. 提交作业
env.execute();
}
}
class MyMap extends RichMapFunction<WaterSensor,String> implements CheckpointedFunction {
Integer count = 0;//每个并行度中都有。
ListState<Integer> countState;//每个并行度中都有
@Override
public String map(WaterSensor value) throws Exception {
return "并行子任务"+getRuntimeContext().getIndexOfThisSubtask() + "处理了"+ ++count+"条数据";
}
@Override
// 开启检查点,检查点执行的时候,会调用这个方法。
public void snapshotState(FunctionSnapshotContext context) throws Exception {
System.out.println(" ================= snapshotState ================= ");
//放之前,先给他清空
countState.clear();
//
countState.add(count);
}
@Override
//初始化状态
public void initializeState(FunctionInitializationContext context) throws Exception {
System.out.println(" ================= initializeState ================= ");
OperatorStateStore operatorStateStore = context.getOperatorStateStore();
// 获取列表状态
ListStateDescriptor listStateDescriptor = new ListStateDescriptor("countState", Integer.class);
countState= operatorStateStore.getListState(listStateDescriptor);
// 容错机制。数据恢复。
if(context.isRestored()){
Integer next = countState.get().iterator().next();
count = next;
}
}
// ================= initializeState =================
// ================= initializeState =================
// ================= snapshotState =================
// ================= snapshotState =================
//2> 并行子任务1处理了1条数据
//1> 并行子任务0处理了1条数据
// ================= snapshotState =================
// ================= snapshotState =================
//2> 并行子任务1处理了2条数据
// ================= snapshotState =================
// ================= snapshotState =================
// ================= snapshotState =================
// ================= snapshotState =================
// ================= snapshotState =================
// ================= snapshotState =================
}
一般什么时候,使用算子状态,从source当中读取数据的时候,或者sink到其他地方的时候的会用到状态。
所以flink中的kafkasource里边维护了状态。也及时kafkasoure的底层,维护了状态,保障了偏移量。
三:联合列表状态
1:两个列表状态的区别
先明确 UnionListState 和你原有 ListState 的核心区别
| 状态类型 | 并行度调整时的行为 | 适用场景 |
|---|---|---|
| ListState | 所有状态项合并后轮询均分给新并行子任务 | 统计、分片计算(需均分状态) |
| UnionListState | 所有状态项合并后广播给每个新并行子任务 | 全量数据依赖(每个子任务都需要完整状态) |
2:案例分析
/**
* UnionListState 案例:
* 1. 每个并行子任务记录自己处理过的所有 WaterSensor 的 id
* 2. 并行度调整/作业重启后,每个新并行子任务能拿到所有历史 id 列表(广播特性)
*/
public class Flink10_OperState_UnionListState {
public static void main(String[] args) throws Exception {
// 1. 准备环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 配置并行度(可以先设2,测试后调整为3验证Union特性)
env.setParallelism(2);
// 开启Checkpoint,保证状态持久化(每10秒一次)
env.enableCheckpointing(10000);
// 3. 读取socket数据
DataStreamSource<String> socketDs = env.socketTextStream("bigdata137", 8888);
// 4. 转换为WaterSensor类型
SingleOutputStreamOperator<WaterSensor> wsDs = socketDs.map(new WaterSensorMapFunction());
// 5. 自定义Map算子,使用UnionListState记录所有id
SingleOutputStreamOperator<String> resultDs = wsDs.map(new MyUnionMap());
// 6. 打印输出
resultDs.print();
// 7. 提交作业
env.execute("UnionListState Demo");
}
/**
* 自定义RichMapFunction,实现CheckpointedFunction
* 核心:使用UnionListState存储所有处理过的WaterSensor id
*/
static class MyUnionMap extends RichMapFunction<WaterSensor, String> implements CheckpointedFunction {
// 存储当前并行子任务处理过的所有id(内存临时存储)
private List<String> currentIdList;
// UnionListState:算子状态,并行度调整时广播所有状态项
private UnionListState<String> unionIdState;
/**
* 初始化:创建内存列表,初始化UnionListState
*/
@Override
public void initializeState(FunctionInitializationContext context) throws Exception {
System.out.println("===== 并行子任务" + getRuntimeContext().getIndexOfThisSubtask() + " 初始化状态 =====");
// 1. 初始化内存临时列表
currentIdList = new ArrayList<>();
// 2. 获取算子状态存储
OperatorStateStore operatorStateStore = context.getOperatorStateStore();
// 3. 定义UnionListState描述符
ListStateDescriptor<String> stateDesc = new ListStateDescriptor<>(
"union_water_sensor_id", // 状态名称
String.class // 状态数据类型
);
// 4. 获取UnionListState(关键:getUnionListState 而非 getListState)
unionIdState = operatorStateStore.getUnionListState(stateDesc);
// 5. 容错恢复:作业重启时,从UnionListState中读取所有id到内存列表
if (context.isRestored()) {
System.out.println("===== 并行子任务" + getRuntimeContext().getIndexOfThisSubtask() + " 恢复状态 =====");
// 遍历UnionListState,将所有历史id加入内存列表
Iterator<String> stateIterator = unionIdState.get().iterator();
while (stateIterator.hasNext()) {
currentIdList.add(stateIterator.next());
}
}
}
/**
* 核心处理逻辑:每条数据加入内存列表,返回当前子任务的id列表信息
*/
@Override
public String map(WaterSensor value) throws Exception {
// 1. 获取当前WaterSensor的id
String sensorId = value.getId();
// 2. 将id加入内存临时列表
currentIdList.add(sensorId);
// 3. 构造返回结果:当前子任务ID + 已处理的id数量 + 所有id列表
return "并行子任务" + getRuntimeContext().getIndexOfThisSubtask()
+ " | 累计处理id数:" + currentIdList.size()
+ " | 所有id:" + currentIdList;
}
/**
* Checkpoint触发时:将内存列表中的所有id写入UnionListState持久化
*/
@Override
public void snapshotState(FunctionSnapshotContext context) throws Exception {
System.out.println("===== 并行子任务" + getRuntimeContext().getIndexOfThisSubtask() + " 持久化状态 =====");
// 1. 清空原有状态(避免重复存储)
unionIdState.clear();
// 2. 将当前内存列表中的所有id写入UnionListState
unionIdState.addAll(currentIdList);
}
}
}
3:总结
托管状态:
描述: 由Flink框架管理状态的存储、序列化以及容错恢复等
算子状态:
作用范围: 算子的每一个并行子任务上(分区、并行度、slot)
类型:
- ListState
- UnionListState
- BroadcastState(重点)
使用步骤:
- 类必须实现CheckpointedFunction接口
- 接口方法:
initializeState: 初始化状态
snapshotState: 对状态进行备份
特点:
- 声明位置: 与普通成员变量声明位置相同
- 作用范围: 与普通成员变量作用范围相同
- 持久化: 可被持久化
- 底层原理: snapshotState阶段将普通变量放入状态中持久化,相当于普通变量也被持久保存
键控状态:
作用范围: 经过keyBy之后的每一个组,组和组之间状态是隔离的
类型:
- ValueState
- ListState
- MapState
- ReducingState
- AggregatingState
使用步骤:
- 声明位置: 在处理函数类成员变量位置声明状态
- 注意: 虽然在成员变量位置声明,但是作用范围是keyBy后的每一个组
- 初始化: 在open方法中对状态进行初始化
- 使用: 在具体的处理函数中使用状态
四:广播状态(重点)
广播状态也是算子状态的一种,作用范围是算子子任务。他的使用方式是固定的。
我们需要处理一条流的数据,需要用到另外一条流的规则。这个时候需要用到广播状态,但是如果两个流,数据两都特别大,这个时候,就不适合使用这个状态了。