一:值状态
1:需求
连续检测水位值,如果同一个传感器的水位值连续两次超过10就报警
2:不使用状态写法
public class Flink01_keyedState_ValueState {
public static void main(String[] args) throws Exception {
// 1. 准备环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 配置并行度
env.setParallelism(1);
// 3. 读取数据
DataStreamSource<String> socketDs = env.socketTextStream("bigdata137", 8888);
// 4. 数据类型转换
SingleOutputStreamOperator<WaterSensor> wsDs = socketDs.map(new WaterSensorMapFunction());
// 5. 按水位id分组
KeyedStream<WaterSensor, String> keyedDs = wsDs.keyBy(WaterSensor::getId);
// 6. 对分组后的数据进行处理
SingleOutputStreamOperator<String> process = keyedDs.process(
new KeyedProcessFunction<String, WaterSensor, String>() {
Map<String,Integer> lastvMap = new HashMap<>();
@Override
public void processElement(WaterSensor value, KeyedProcessFunction<String, WaterSensor, String>.Context ctx, Collector<String> out) throws Exception {
Integer vc = value.getVc();
String currentKey = ctx.getCurrentKey();
Integer i = lastvMap.get(value.getId());
i = i == null ? 0 : i;
if(Math.abs(vc - i) > 10 ){
out.collect("传感器id"+currentKey+"是当前水位值"+vc+"和上一次水位值"+i+"之差>10");
}
lastvMap.put(ctx.getCurrentKey(),i);
}
}
);
//7. 打印输出
process.print();
//8. 提交作业
env.execute();
}
}
当前的问题,使用普通变量,当前的子任务组都共用一个变量,这个作用范围是非常危险的。
3:使用值状态
值状态就是ValueState
package com.dashu.day08;
import com.dashu.bean.WaterSensor;
import com.dashu.bean.WaterSensorMapFunction;
import org.apache.flink.api.common.functions.RuntimeContext;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
import java.util.HashMap;
import java.util.Map;
public class Flink01_keyedState_ValueState2 {
public static void main(String[] args) throws Exception {
// 1. 准备环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 配置并行度
env.setParallelism(1);
// 3. 读取数据
DataStreamSource<String> socketDs = env.socketTextStream("bigdata137", 8888);
// 4. 数据类型转换
SingleOutputStreamOperator<WaterSensor> wsDs = socketDs.map(new WaterSensorMapFunction());
// 5. 按水位id分组
KeyedStream<WaterSensor, String> keyedDs = wsDs.keyBy(WaterSensor::getId);
// 6. 对分组后的数据进行处理
SingleOutputStreamOperator<String> process = keyedDs.process(
new KeyedProcessFunction<String, WaterSensor, String>() {
//声明值状态
// 注意:虽然声明在成员百年来那个的位置,作用范围不是每一个子任务,而是keyby之后的每一组。这个事flink自己就帮我们做了,
// 注意:不能在声明的时候直接对状态进行初始化。(初始化是在对象创建的时候,这个时候算子的生命周期还没开始,拿不到运行时上下文。)
ValueState<Integer> lastVcState;
@Override
// 复函数,有一个open方法。给状态进行初始化。
public void open(Configuration parameters) throws Exception {
// 对于键控状态来讲,在oepn方法中进行初始刷
// 获取运行时上下文
RuntimeContext runtimeContext = getRuntimeContext();
// 值状态描述器。
ValueStateDescriptor<Integer> lastVsState = new ValueStateDescriptor<>("lastVsState", Integer.class);
lastVcState= runtimeContext.getState(lastVsState);
}
@Override
public void processElement(WaterSensor value, KeyedProcessFunction<String, WaterSensor, String>.Context ctx, Collector<String> out) throws Exception {
//1. 获取当前水位值
Integer curVC = value.getVc();
//2. 获取上次水位值
Integer lastVc = lastVcState.value();
lastVc = lastVc == null? 0:lastVc;
//3. 获取key
String currentKey = ctx.getCurrentKey();
// 更新状态
// Integer value1 = lastVcState.update();
// // 清理掉状态
// Integer value1 = lastVcState.clear();
if(Math.abs(curVC -lastVc) > 10 ){
out.collect("传感器id"+currentKey+"是当前水位值"+lastVc+"和上一次水位值"+lastVc+"之差>10");
}
//4. 将当前水位值更新到水位中
lastVcState.update(curVC);
}
}
);
//7. 打印输出
process.print();
//8. 提交作业
env.execute();
}
}
输入内容:
[root@bigdata137 ~]# nc -lk 8888
ws1,1,1
ws1,1,10
ws1,10,1
ws1,100,100
ws2,1,1
ws2,10,1
ws2,11,11
ws2,100,111
ws3,1,1
ws3,1,100
输出内容:
传感器idws1是当前水位值1和上一次水位值1之差>10
传感器idws2是当前水位值11和上一次水位值11之差>10
传感器idws3是当前水位值1和上一次水位值1之差>10
4:总结
针对算子内部每一组都维护了状态。彼此之间不相干,不干扰。
二:列表状态
1:需求
输出每种传感器最高的3个水位值。
2:使用列表状态
核心内容提取:ListState<T> 接口与操作方法
- 基本定位
ListState<T> 是用于以列表形式组织和管理状态数据 的接口,其中泛型 T 表示列表中存储的数据类型,其操作方式与普通 List 集合高度相似。
- 核心方法
Iterable<T> get():获取当前列表状态,返回一个可迭代的Iterable<T>类型对象。update(List<T> values):传入一个列表values,直接覆盖当前状态列表。add(T value):向状态列表中添加单个元素value。addAll(List<T> values):向状态列表中批量添加多个元素 ,以列表values的形式传入。
- 状态描述器
ListState 对应的状态描述器为 ListStateDescriptor,其用法与 ValueStateDescriptor 完全一致。
3:案例示例
// 输出每种传感器最高的3个水位值。
public class Flink01_keyedState_ListState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
SingleOutputStreamOperator<WaterSensor> sensorDS = env
.socketTextStream("bigdata137", 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
// 在这个open方法当中对状态进行初始化。
public void open(Configuration parameters) throws Exception {
//获取运行时上下文+获取ListState+添加ListState描述器+创建状态描述器的时候需要指定状态名称
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();
}
}
4:案例分析
从结果上可以清晰的看到,State和State之间是天然隔离的。不会数据共享
三:Map状态
1:需求
统计每种传感器的每种水位值出现的次数
2:使用Map状态
package com.dashu.day08;
import com.dashu.bean.WaterSensor;
import com.dashu.bean.WaterSensorMapFunction;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
import org.apache.flink.api.common.state.MapState;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
// 统计每种传感器的每种水位值出现的次数。
public class Flink04_keyedState_MapState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
SingleOutputStreamOperator<WaterSensor> sensorDS = env
.socketTextStream("bigdata137", 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>() {
MapState<Integer,Integer> vcCountMapState;
@Override
// 在这个open方法当中对状态进行初始化。
public void open(Configuration parameters) throws Exception {
//获取运行时上下文+获取ListState+添加ListState描述器+创建状态描述器的时候需要指定状态名称
vcCountMapState = getRuntimeContext().getMapState(new MapStateDescriptor<>("vcCountMapState", Types.INT,Types.INT));
}
@Override
public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
// 1.判断是否存在vc对应的key
Integer vc = value.getVc();
if (vcCountMapState.contains(vc)) {
// 1.1 如果包含这个vc的key,直接对value+1
Integer count = vcCountMapState.get(vc);
vcCountMapState.put(vc, ++count);
} else {
// 1.2 如果不包含这个vc的key,初始化put进去
vcCountMapState.put(vc, 1);
}
// 2.遍历Map状态,输出每个k-v的值
StringBuilder outStr = new StringBuilder();
outStr.append("======================================\n");
outStr.append("传感器id为" + value.getId() + "\n");
for (Map.Entry<Integer, Integer> vcCount : vcCountMapState.entries()) {
outStr.append(vcCount.toString() + "\n");
}
outStr.append("======================================\n");
out.collect(outStr.toString());
// vcCountMapState.get(); // 对本组的Map状态,根据key,获取value
// vcCountMapState.contains(); // 对本组的Map状态,判断key是否存在
// vcCountMapState.put(, ); // 对本组的Map状态,添加一个 键值对
// vcCountMapState.putAll(); // 对本组的Map状态,添加多个 键值对
// vcCountMapState.entries(); // 对本组的Map状态,获取所有键值对
// vcCountMapState.keys(); // 对本组的Map状态,获取所有键
// vcCountMapState.values(); // 对本组的Map状态,获取所有值
// vcCountMapState.remove(); // 对本组的Map状态,根据指定key,移除键值对
// vcCountMapState.isEmpty(); // 对本组的Map状态,判断是否为空
// vcCountMapState.iterator(); // 对本组的Map状态,获取迭代器
// vcCountMapState.clear(); // 对本组的Map状态,清空
}
}
)
.print();
env.execute();
}
}
3:案例分析
四:Reducing状态
1:案例
统计每种传感器的每种传感器的水位和
2:代码
// 统计每种传感器的每种传感器的水位和
public class Flink05_keyedState_ReduceState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(3);
SingleOutputStreamOperator<WaterSensor> sensorDS = env
.socketTextStream("bigdata137", 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>() {
@Override
public void processElement(WaterSensor value, KeyedProcessFunction<String, WaterSensor, String>.Context ctx, Collector<String> out) throws Exception {
Integer vc = value.getVc();
// 我们这里调用的是add方法实际会调用open中的传入的计算方法。
sumVcState.add(vc);
// 从状态中获取累加结果
Integer i = sumVcState.get();
out.collect("当前传感器"+ctx.getCurrentKey()+"水位和是:"+i);
}
private ReducingState<Integer> sumVcState;
@Override
public void open(Configuration parameters) throws Exception {
ReducingStateDescriptor<Integer> sumVcState1 = new ReducingStateDescriptor<>("sumVcState", new ReduceFunction<Integer>() {
@Override
public Integer reduce(Integer value1, Integer value2) throws Exception {
return value1+value1;
}
}, Integer.class);
sumVcState = this.getRuntimeContext().getReducingState(sumVcState1);
}
}
)
.print();
env.execute();
}
}
3:案例分析
[root@bigdata137 ~]# nc -lk 7777
ws1,1,10
ws1,1,20
ws2,1,10
ws2,1,11
ws1,1,15
ws2,1,5
ws3,1,1
3> 当前传感器ws1水位和是:10
3> 当前传感器ws1水位和是:20
2> 当前传感器ws2水位和是:10
2> 当前传感器ws2水位和是:20
3> 当前传感器ws1水位和是:40
2> 当前传感器ws2水位和是:40
3> 当前传感器ws3水位和是:1
五:聚合状态
1:定义
与归约状态非常类似,聚合状态也是一个值,用来保存添加进来的所有数据的聚合结果。与 ReducingState 不同的是,它的聚合逻辑是由在描述器中传入一个更加一般化的聚合函数(AggregateFunction)来定义的;这也就是之前我们讲过的 AggregateFunction,里面通过一个累加器(Accumulator)来表示状态,所以聚合的状态类型可以跟添加进来的数据类型完全不同,使用更加灵活。
同样地,AggregatingState 接口调用方法也与 ReducingState 相同,调用.add () 方法添加元
2:代码查看
package com.dashu.day08;
import com.dashu.bean.WaterSensor;
import com.dashu.bean.WaterSensorMapFunction;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.ReduceFunction;
import org.apache.flink.api.common.state.AggregatingState;
import org.apache.flink.api.common.state.AggregatingStateDescriptor;
import org.apache.flink.api.common.state.ReducingState;
import org.apache.flink.api.common.state.ReducingStateDescriptor;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
import java.time.Duration;
// 计算每种传感器的水位平均水位
public class Flink06_keyedState_AggregatingState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(3);
SingleOutputStreamOperator<WaterSensor> sensorDS = env
.socketTextStream("bigdata137", 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>() {
private AggregatingState<Integer,Double> vcAvgState;
@Override
public void open(Configuration parameters) throws Exception {
AggregatingStateDescriptor<Integer, Tuple2<Integer, Integer>, Double> descriptor =
new AggregatingStateDescriptor<Integer, Tuple2<Integer, Integer>, Double>(
"vcAvgState",
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) {
accumulator.f0 += value;
accumulator.f1 +=1;
return accumulator;
}
@Override
public Double getResult(Tuple2<Integer, Integer> accumulator) {
Integer f0 = accumulator.f0;
Integer f1 = accumulator.f1;
return f0*1d/f1;
}
@Override
public Tuple2<Integer, Integer> merge(Tuple2<Integer, Integer> a, Tuple2<Integer, Integer> b) {
return null;
}
},
// 累加器的类型
Types.TUPLE(Types.INT,Types.INT)
);
vcAvgState = getRuntimeContext().getAggregatingState(descriptor);
}
@Override
public void processElement(WaterSensor value, KeyedProcessFunction<String, WaterSensor, String>.Context ctx, Collector<String> out) throws Exception {
Integer vc = value.getVc();
//将当前水位值放到状态当中,实际上调用的是上边我们自己写的方法。
vcAvgState.add(vc);
// 从状态中获取水位值
Double v = vcAvgState.get();
out.collect("传感器iD="+ctx.getCurrentKey()+"的水位平均值是:"+v);
}
}
)
.print();
env.execute();
}
}
3:案例分析
ws1,1,10
ws1,1,20
ws1,1,30
ws1,1,40
ws2,1,10
ws2,1,10
ws2,1,20
3> 当前传感器ws1水位和是:10
3> 当前传感器ws1水位和是:20
3> 当前传感器ws1水位和是:40
3> 当前传感器ws1水位和是:80
2> 当前传感器ws2水位和是:10
2> 当前传感器ws2水位和是:20
2> 当前传感器ws2水位和是:40
七:总结
状态:Flink替你开辟内存,替你转状态进行序列化,替你进行容错恢复。
状态:
用途: 用于保存程序运行的中间结果
状态分类:
原始状态:
描述: 由程序员自己开辟内存,自己负责状态的序列化以及容错恢复等
托管状态:
描述: 由Flink框架管理状态的存储、序列化以及容错恢复等
子分类:
算子状态:
作用范围: 算子的每一个并行子任务上(分区、并行度、slot)
键控状态:
作用范围: 经过keyBy之后的每一个组,组和组之间状态是隔离的
具体类型:
- ValueState
- ListState
- MapState
- ReducingState
- AggregatingState
使用步骤:
- 在处理函数类成员变量位置声明状态,注意:虽然在成员变量位置声明,但是作用范围是keyBy后的每一个组
- 在open方法中对状态进行初始化
- 在具体的处理函数中使用状态