Flink中的状态
-
什么是状态?
由一个任务维护,并且用来计算某个结果的所有数据,都属于这个任务的状态可以认为状态就是一个本地变量,可以被任务的业务逻辑访问
Flink 会进行状态管理,包括状态一致性、故障处理以及高效存储和访问,以便开发人员可以专注于应用程序的逻辑
-
有些算子有些任务是没有状态的,如map操作,只跟输入数据有关。像窗口操作不管是增量窗口函数还是全窗口函数都要保持里面的信息的,一开始在窗口到达结束时间之前是不输出数据的,所以最后输出数据的时候,他的计算是要依赖之前的,全窗口可以认为是把所有数据都作为状态保存下来。增量聚合窗口来一个聚合一次要保存的是中间聚合状态。像ProcessFunction可以有状态也可以没有状态。
-
无状态流处理和有状态流处理的主要区别:无状态流处理分别接收每条输入数据,根据最新输入的数据生成输出数据;有状态流处理会维护状态,根据每条输入记录进行更新,并基于最新输入的记录和当前的状态值生成输出记录,即综合考虑多个事件之后的结果。
状态的分类
Operator State
-
每个算子状态绑定到一个并行算子实例,作用范围限定为算子任务,同一并行任务的状态是共享的,并行处理的所有数据都可以访问到相同的状态。Kafka Connector就是使用算子状态的很好的一个例子,Kafka consumer的每个并行实例都维护一个主题分区和偏移,作为算子状态。当并行性发生变化时,算子状态接口支持在并行运算符实例之间重新分配状态。可以有不同的方案来进行这种再分配。
-
因为同一个并行任务处理的所有数据都可以访问到当前的状态,所以就相当于本地变量
-
算子状态有3种基本数据结构:①列表状态(List state):状态表示为一组数据的列表②联合列表状态(Union list state):也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复。③广播状态(Broadcast state):如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。那就可以访问到别的并行子任务的状态。
-
算子状态运用的时候可能应用场景没那么多,一般都是keyby之后根据不同的key做分区讨论。如果所有数据来了全部统一处理的话一般还要划分成不同的状态要保存为链表,并行度调整的时候可以根据这个列表拆开,做进一步调整。
-
算子状态数据结构
1️⃣:列表状态(List state):将状态表示为一组数据的列表
2️⃣:联合列表状态(Union list state):也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复
3️⃣:广播状态(Broadcast state):如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。
4️⃣:联合列表状态与列表状态的区别:主要是并行度调整状态怎样重新分配,列表状态本身分配的时候直接分配;联合列表状态的话就是把所有元素都联合起来,然后由每个任务自己定义最后留下哪些,也就是自己截取要哪一部分。
-
编写代码统计出每个分区内的数据
数据展示
sensor_1,1547718199,35.8 sensor_6,1547718201,15.4 sensor_7,1547718202,6.7 sensor_10,1547718205,38.1 sensor_1,1547728199,25.8 sensor_6,1547712201,35.4 sensor_7,1547718102,16.7 sensor_10,1547712205,28.1
自定义类
package beans;
/**
* 传感器温度读数的数据类型
*/
public class SenSorReading {
private String id;
private Long timeStamp;
private Double temperature;
public SenSorReading() {
}
public SenSorReading(String id, Long timeStamp, Double temperature) {
this.id = id;
this.timeStamp = timeStamp;
this.temperature = temperature;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Long getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(Long timeStamp) {
this.timeStamp = timeStamp;
}
public Double getTemperature() {
return temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
}
@Override
public String toString() {
return "SenSorReading{" +
"id='" + id + '\'' +
", timeStamp=" + timeStamp +
", temperature=" + temperature +
'}';
}
}
程序代码实现
package State;
import 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.DataStreamSource;
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 StateTest01 {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<String> inputStream = env.readTextFile("src/main/resources/sensor.txt");
SingleOutputStreamOperator<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
public static class MyCountMapper implements MapFunction<SenSorReading, Integer>, ListCheckpointed<Integer> {
//第一一个本地变量,作为算子状态
private Integer count = 0;
@Override
public Integer map(SenSorReading senSorReading) throws Exception {
count++;
return count;
}
/**
* 对状态做快照
*
* @param l
* @param l1
* @return
* @throws Exception
*/
@Override
public List<Integer> snapshotState(long l, long l1) throws Exception {
return Collections.singletonList(count);
}
/**
* 发生故障时
*
* @param list
* @throws Exception
*/
@Override
public void restoreState(List<Integer> list) throws Exception {
for (Integer num : list) {
count += num;
}
}
}
}
结果展示
Keyed State
-
键控状态是根据输入数据流中定义的键(key)来维护和访问的
-
Flink 为每个 key 维护一个状态实例,并将具有相同键的所有数据,都分区到同一个算子任务中,这个任务会维护和处理这个 key 对应的状态
-
当任务处理一条数据时,它会自动将状态的访问范围限定为当前数据的 key
- 键控状态数据结构
- 写出相同key的个数
package State;
import beans.SenSorReading;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.state.*;
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;
/**
* 只针对key生效
* 比如本体的计算key相同的个数
*/
public class State_KeyedState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<String> inputStream = env.readTextFile("src/main/resources/sensor.txt");
SingleOutputStreamOperator<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
public static class MyKeyCountMapper extends RichMapFunction<SenSorReading, Integer> {
private ValueState<Integer> keyCountState;
//其他类型状态的声明
// private ListState<String> myListState;
// private MapState<String, Double> myMapState;
//
// private ReducingState<SenSorReading> myReducing;
//getRuntimeContext是在open方法之后才生成的
@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));
// myMapState = getRuntimeContext().getMapState(new MapStateDescriptor<String, Double>("my-map", String.class, Double.class));
// myReducing = getRuntimeContext().getMapState(new ReducingStateDescriptor<SenSorReading>("my-reducing", SenSorReading.class));
}
@Override
public Integer map(SenSorReading senSorReading) throws Exception {
Integer count = keyCountState.value();
count++;
keyCountState.update(count);
//其他状态的api调用
// Iterable<String> strings = myListState.get();
// myListState.add("hello");
//
// myMapState.put("a", 1.1);
//
// myReducing.add(senSorReading);
return count;
}
}
}
- 结果展示
状态后端(State Backends)
-
每传入一条数据,有状态的算子任务都会读取和更新状态
-
由于有效的状态访问对于处理数据的低延迟至关重要,因此每个并行任务都会在本地维护其状态,以确保快速的状态访问
-
状态的存储、访问以及维护,由一个可插入的组件决定,这个组件就叫做状态后端(state backend)
-
状态后端主要负责两件事:本地的状态管理,以及将检查点(checkpoint)状态写入远程存储
-
状态后端的分类
1️⃣:MemoryStateBackend:内存级的状态后端,会将键控状态作为内存中的对象进行管理,将它们存储在TaskManager 的 JVM 堆上,而将 checkpoint 存储在 JobManager 的内存中,特点:快速、低延迟,但不稳定
2️⃣:FsStateBackend:将 checkpoint 存到远程的持久化文件系统(FileSystem)上,而对于本地状态,跟 MemoryStateBackend 一样,也会存在 TaskManager 的 JVM 堆上,同时拥有内存级的本地访问速度,和更好的容错保证
3️⃣:RocksDBStateBackend:将所有状态序列化后,存入本地的 RocksDB 中存储。
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
//状态后端的配置
env.setStateBackend(new MemoryStateBackend());//存到内存
env.setStateBackend(new FsStateBackend(""));//存储到文件
env.setStateBackend(new RocksDBStateBackend(""));//序列化后存储到本地的RocksDB