目录
Flink 状态管理
1 Flink 中的状态
2 算子状态(Operator State)
2.1 算子状态数据结构
3 键控状态(Keyed State)
3.1键控状态数据结构
3.2 键控状态的使用
Case
4 状态后端(State Backends)
4.1 选择一个状态后端
Flink 状态管理
1 Flink 中的状态
- 由一个任务维护,并且用来计算某个结果的所有数据,都叫做这个任务的状态
- 可以认为状态就是一个本地变量,可以被任务的业务逻辑访问
- Flink 会进行状态管理,包括状态一致性、故障处理以及高效存储和访问,以便开发人员可以专注于应用程序的逻辑
- 在 Flink 中,状态始终与特定算子相关联
- 为了使运行时的 Flink 了解算子的状态,算子需要预先注册其状态
➢ 总的说来,有两种类型的状态: - 算子状态(Operator State)
• 算子状态的作用范围限定为算子任务 - 键控状态(Keyed State)
• 根据输入数据流中定义的键(key)来维护和访问
2 算子状态(Operator State)
• 算子状态的作用范围限定为算子任务,由同一并行任务所处理的所有数据都 可以访问到相同的状态
• 状态对于同一子任务而言是共享的
• 算子状态不能由相同或不同算子的另一个子任务访问
2.1 算子状态数据结构
➢ 列表状态(List state)
• 将状态表示为一组数据的列表
➢ 联合列表状态(Union list state)
• 也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复
➢ 广播状态(Broadcast state)
• 如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。
package com.dongda.state;
import com.dongda.beans.SensorReading;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.streaming.api.checkpoint.ListCheckpointed;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import java.util.Collections;
import java.util.List;
public class StateTest1_OperatorState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);//为了方便观察打印出来的结果,将全局并行度设置为1
//从文件里面读取数据
DataStream<String> inputStream = env.readTextFile("/Users/haitaoyou/developer/flink/src/main/resources/sensor.txt");
//lamda表达式写法
DataStream<SensorReading> dataStream = inputStream.map(line -> {
String[] fields = line.split(",");
return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
});
//定义一个有状态的map操作,统计当前分区数据个数
SingleOutputStreamOperator<Integer> resultStream = dataStream.map(new MyCountMapper());
resultStream.print();
env.execute();
}
//自定义MapFunction
private static class MyCountMapper implements MapFunction<SensorReading,Integer>, ListCheckpointed<Integer> {
//定义一个本地变量,作为算子状态
private Integer count = 0;
@Override
public Integer map(SensorReading sensorReading) throws Exception {
count++;
return count;
}
@Override
public List<Integer> snapshotState(long l, long l1) throws Exception {
return Collections.singletonList(count);
}
@Override
public void restoreState(List<Integer> list) throws Exception {
for (Integer num:list){
count+=num;
}
}
}
}
算子状态主要对当前分区任务的状态去做一个维护和访问,所以他有一个特点就是key对他没有关系,所以当前针对不同的key,例如我们的sensor_id,针对不同的id要去做一个聚合统计、要去得到他的一个状态,算子状态就不太合适。
3 键控状态(Keyed State)
1)键控状态是根据输入数据流中定义的键(key)来维护和访问的
2)Flink 为每个 key 维护一个状态实例,并将具有相同键的所有数据,都分区到同一个算子任务中,这个任务会维护和处理这个 key 对应的状态
3)当任务处理一条数据时,它会自动将状态的访问范围限定为当前数据的 key
3.1键控状态数据结构
➢ 值状态(Value state)
• 将状态表示为单个的值
➢ 列表状态(List state)
• 将状态表示为一组数据的列表
➢ 映射状态(Map state)
• 将状态表示为一组 Key-Value 对
➢ 聚合状态(Reducing state & Aggregating State)
• 将状态表示为一个用于聚合操作的列表
3.2 键控状态的使用
实例代码如下:
package com.dongda.state;
import com.dongda.beans.SensorReading;
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.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
public class StateTest2_KeyedState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);//为了方便观察打印出来的结果,将全局并行度设置为1
//从文件里面读取数据
DataStream<String> inputStream = env.readTextFile("/Users/haitaoyou/developer/flink/src/main/resources/sensor.txt");
//lamda表达式写法
DataStream<SensorReading> dataStream = inputStream.map(line -> {
String[] fields = line.split(",");
return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
});
//定义一个有状态的map操作,统计当前sensor个数
SingleOutputStreamOperator<Integer> resultStream = dataStream.keyBy("id")
.map(new MyKeyCountMapper());
resultStream.print();
env.execute();
}
//自定义RichMapFunction
private static class MyKeyCountMapper extends RichMapFunction<SensorReading,Integer> {
private ValueState<Integer> keyCountState;
//其他类型状态的声明
private ListState<String> myListState;
@Override
public void open(Configuration parameters) throws Exception {
keyCountState=getRuntimeContext().getState(new ValueStateDescriptor<Integer>("key-count",Integer.class,0));
myListState=getRuntimeContext().getListState(new ListStateDescriptor<String>("my-list",String.class));
}
@Override
public Integer map(SensorReading sensorReading) throws Exception {
Integer count = keyCountState.value();
count++;
keyCountState.update(count);
//其他状态API调用
//listState
for (String str:myListState.get()){
System.out.println(str);
}
return count;
}
}
}
Case
我们利用Keyed State,实现这样一个需求:检测传感器的温度值,如果连续的两个温度差值超过10度,就输出报警。
package com.dongda.state;
import com.dongda.beans.SensorReading;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.functions.RichFlatMapFunction;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.configuration.Configuration;
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 org.apache.flink.util.Collector;
public class StateTest3_KeyedStateApplicationCase {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
//从文件里面读取数据
DataStreamSource<String> inputStream = env.readTextFile("/Users/haitaoyou/developer/flink/src/main/resources/sensor.txt");
//转换成sensorReading类型
SingleOutputStreamOperator<SensorReading> dataStream = inputStream.map(new MapFunction<String, SensorReading>() {
@Override
public SensorReading map(String s) throws Exception {
String[] fields = s.split(",");
return new SensorReading(fields[0],new Long(fields[1]),new Double(fields[2]));
}
});
//按照id进行分组,然后进行faltMap操作,检测温度跳变,输出报警
SingleOutputStreamOperator<Tuple3<String, Double, Double>> resultStream = dataStream.keyBy("id")
.flatMap(new MyFaltMap(10.0));
resultStream.print();
env.execute();
}
private static class MyFaltMap extends RichFlatMapFunction<SensorReading, Tuple3<String,Double,Double>> {
//私有属性,温度跳变阀值
private Double temp;
//定义ValueState,上一次的温度值
private ValueState<Double> valueState;
@Override
public void open(Configuration parameters) throws Exception {
valueState=getRuntimeContext().getState(new ValueStateDescriptor<Double>("last-temp",Double.class));//描述器
}
@Override
public void close() throws Exception {
valueState.clear();
}
public MyFaltMap(double temp) {
this.temp=temp;
}
@Override
public void flatMap(SensorReading sensorReading, Collector<Tuple3<String, Double, Double>> collector) throws Exception {
temp=sensorReading.getTemperature();
//如果状态值不为null,那么就判断两次的温度值
if (valueState.value()!=null){
Double diff=Math.abs(temp-valueState.value());
if (diff>=10){
collector.collect(new Tuple3<>(sensorReading.getId(),valueState.value(),temp));
}
}
//更新状态
valueState.update(temp);
}
}
}
4 状态后端(State Backends)
• 每传入一条数据,有状态的算子任务都会读取和更新状态
• 由于有效的状态访问对于处理数据的低延迟至关重要,因此每个并行任务都会在本地维护其状态,以确保快速的状态访问
• 状态的存储、访问以及维护,由一个可插入的组件决定,这个组件就叫做状态后端(state backend)
• 状态后端主要负责两件事:本地的状态管理,以及将检查点(checkpoint)状态写入远程存储
4.1 选择一个状态后端
➢ MemoryStateBackend
- 内存级的状态后端,会将键控状态作为内存中的对象进行管理,将它们存储在 TaskManager 的 JVM 堆上,而将 checkpoint 存储在 JobManager 的内存 中
- 特点:快速、低延迟,但不稳定
➢ FsStateBackend
- 将 checkpoint 存到远程的持久化文件系统(FileSystem)上,而对于本地状 态,跟 MemoryStateBackend 一样,也会存在 TaskManager 的 JVM 堆上
- 同时拥有内存级的本地访问速度,和更好的容错保证
➢ RocksDBStateBackend
• 将所有状态序列化后,存入本地的 RocksDB 中存储。