状态管理和容错机制
参考文章
1.状态编程介绍
1.1 有状态计算 Vs 无状态计算
流式计算分为无状态和有状态两种情况:
(1)无状态的计算基于每个独立事件给出每个事件的结果
例如,流处理应用程序从传感器接收温度读数,并在温度超过90度时发出警告。
(2)有状态的计算则会基于多个事件输出结果
1.所有类型的窗口。例如,计算过去一小时的平均温度,就是有状态的计算。
2.所有用于复杂事件处理的状态机。例如,若在一分钟内收到两个相差20度以上的温度读数,则发出警告,这是有状态的计算。
3.流与流之间的所有关联操作、流与静态表、动态表之间的关联操作
图示无状态流处理和有状态流处理的主要区别:
- 无状态流处理分别接收每条数据记录(图中的黑条),然后根据最新输入的数据生成输出数据(白条)。
- 有状态流处理会维护状态(根据每条输入记录进行更新),并基于最新输入的记录和当前的状态值生成输出记录(灰条)。
尽管无状态的计算很重要,但是流处理对有状态的计算更感兴趣。事实上,正确地实现有状态的计算比实现无状态的计算难得多。旧的流处理系统并不支持有状态的计算,而新一代的流处理系统则将状态及其正确性视为重中之重。
Flink所有算子都是有状态计算,而Spark默认是无状态计算,updateStandBykey算子是有状态但是不好用
1.2 状态的作用
1.Job故障重启(程序回复)
2.升级程序(修改bug)
3.做增量计算(聚合操作)
1.3 Flink状态的学习模块
2. 状态的数据结构
flink的状态有两种表现形式
这里只学习托管状态;
flink 托管状态的数据结构图
- ValueState:存一个value
- ListState:存多个value
- MapState:存多个key-value类型数据
- ReducingState:用于聚合
- FoldingState:用于聚合
- AggregatingState:用于聚合
3. 状态的分类
3.1 KeyedState
- KeyedState 是以Key为单位进行访问和维护的
特点:
- 只有keyedStream才能用;但是KeyedStream也可以使用算子状态
- 键控状态是根据输入数据流中定义的键(key)来维护和访问的。( 一个key一个状态)
- 注意无论是键控状态还是算子状态,状态的存储都是以并行度为单位的;只不过键控状态一个并行度中存储多个key的状态;
支持的数据类型
ValueState<T>
保存单个的值,值的类型为T。
o get操作: ValueState.value()
o set操作: ValueState.update(T value)
ListState<T>
保存一个列表,列表里的元素的数据类型为T。基本操作如下:
o ListState.add(T value)
o ListState.addAll(List values) (增加:老的list+新的list)
o ListState.get()返回Iterable
o ListState.update(List values) (替换:用新的list替换老的list)
MapState<K, V>
保存Key-Value对
o MapState.get(UK key)
o MapState.put(UK key, UV value)
o MapState.contains(UK key)
o MapState.remove(UK key)
o MapState.value()
ReducingState<T>
保存一个值,该值表示添加到该状态的所有值的聚合结果;
o add(T)
AggregatingState<IN, OUT>
保存一个值,该值表示添加到该状态的所有值的聚合结果;
和ReducingState的区别是 添加进来的值的类型和聚合的结果的类型可以不同
o add(T)
KeyedState的横向扩展
https://www.jianshu.com/p/ba2a6ee12e62
keyed state 相比于 operator state 有个明显的优势:当进行扩/缩容时,可以很容易找出状态与并行实例之间的映射关系。状态的重分配只需要和 keyed stream 分区保持一致即可。在扩/缩容之后,key state 需要分配到对应并行实例中,keyed stream 的 hash 策略确定了 keyed state 与新并行实例之间的映射关系。
尽管 keyed state 顺利的解决了在扩/缩容时状态与 sub-tasks 之间映射的问题,但还留下了另一个问题:如何能高效的将状态传送到 subtasks 本地?
-
如果我们没有进行扩/缩容,那么每一个 subtask 可以简单的顺序拉取对应的状态。
-
但是在扩/缩容时,我们无法再进行简单的顺序读取:新 subtask 所属的状态数据可能会散布在所有的状态文件中(可以考虑一下,当你改变并行度之后 hash(key) mod parallelism 会发生什么样的变化)。通过图 3A我们可以清楚的看到,在并行度改变后状态数据读取过程发生巨大变化。这个示例中,我们展示了并行度从 3 变到 4 时,key(key 的范围:0~20) 的 shuffler 过程。
一个幼稚解决方法
- 让新 subtasks 读取全部状态,然后从中再过滤出属于自己 key 的状态数据。尽管这种方式可以充分利用顺序读取带来的效率,但每个 subtask 将会读取大量与自身无关的状态数据,并且还会给分布式存储系统带来大量的读取请求。
另一种方法
在 checkpoint 时构建一个 keyed state 存储索引。这种方法可以让 subtasks 精准的读取到自己 key 所属的状态数据,从而避免读取大量无关数据。但这种方法也存在 2 个不足:
- 所有 key 的索引信息(即 key 到存储位置和偏移量的映射)数据量可能会非常大;
- 另外,这种方法会导致大量的随机读写(random I/O)(如图 3A 所示,在寻找对应 key 的状态数据时,随机读写大大降低了分布式文件系统的性能)。
Flink 的最终的方法是介于以上这两个极端方法之间
-
引入 key-group 作为状态分配的最小单元。
那么它是如何工作的呢?key-group 的数量必须在任务启动之前指定,且一旦指定后将无法再做更改。由于 key-group 是状态分配的最小单元,这也意味着 key-group 的数量就是并行度的上限。简单来说,key-groups 是并行度的灵活性(它限制了并行度的上限)与索引和状态恢复开销之间的一种权衡。 -
我们将 key-groups 作为 key 范围分配给 subtasks。因此 subtasks 是顺序读取,通常这会涉及到多个 key-groups 的顺序读取过程。这种方式的另一个优势在于:减少了索引的元数据,元数据中只需要记录 key-group 到 subtask 映射关系。我们再不需要存储 key-to-subtask 明细列表,因为 key-group 的范围信息足够说明 key 的边界以应对扩/缩容。
在图 3B 中展示了 10 个 key-groups 在并行度从 3 到 4 的过程。我们可以看到,通过引入key-groups 以及范围重分配的方式极大的提升了访问效率。图 3B 中的计算公式 2 和计算公式 3 还详细的说明了 key-groups 的计算和分配过程。
keyedState横向扩展机制总结
- 保障key 和 keyState 能够绑定在同一个并行度上即可;这个很好实现,都按照相同的hash规则即可保证
- state重新分配时效率优化:需要建立索引,{key:[start,end]},如果以单个keystate 作为分配的基本,索引会很大!flink将key打包成KeyGroup,一个keyGroup中有多个{key:[start,end]},分配的时候以keyGroup进行分配;【批量分配】 提高效率
- 注意: key-group 的个数 >= 最大并行度,所以如果想恢复任务 state,最大并行度是不能修改的。大家需要提前预估最大并行度个数
TTL设置
1. KeyedState的超时策略
- KeyedState太多,所以flink提供了针对KeyedState TTL的设置
- 任何类型的KeyedState都可以设置TTL。
- 所有State Collection都支持条目级别的TTL,也就是list,map中的条目独立expire
2.过期状态是如何清理的
1.不可能遍历所有keyState,找到过期的进行清理
2.在checkpoint的时候,list不检查,全部输出,只在keyState被使用的时候,现查,当前时间 - 设置时间 > TTL
3. 过期的keyState还有机会能够拿到,基于配置
3.KeyedState TTL使用
//KeyedState TTL设置
StateTtlConfig ttlConfig = StateTtlConfig.newBuilder(Time.seconds(1))
.setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
.setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
.build();
//在状态描述器中设置TTL
ValueStateDescriptor<String> stateDescriptor = new ValueStateDescriptor<>("text state", String.class);
stateDescriptor.enableTimeToLive(ttlConfig);
Refresh策略(如何更新keyedState最后访问时间)
- StateTtlConfig.UpdateType.Disabled 禁用TTL 永不过期
- StateTtlConfig.UpdateType.OnCreateAndWrite 每次写操作更新State最后访问时间
- StateTtlConfig.UpdateType.OnReadAndWrite 每次读写都会更新
状态可见性
- StateTtlConfig.StateVisibility.NeverReturnExpired 永不返回过期状态
- StateTtlConfig.StateVisibility.ReturnExpiredIfNotCleanedUp 可以返回已经过期但是并未清理的状态值
TTL时间语义:
- 只支持processingtime
- setTimeCharacteristic(TimeCharacteristic.timeCharacteristic)
4. 过期状态清理策略
1.default
- 读取的时候才会被清理;弊端就是可能会导致状态越来越大
2.FULL_STATE_SCAN_SNAPSHOT
- Checkpoint的时候清理full snapshot中的过期状态
- 不适用于RocksDB state backend上的增量checkpoint
5. TTL注意事项
- 启用TTL会增加状态后端存储消耗
- 原本没有启用TTL,后来启用TTL做恢复会导致兼容性失败和StatmigrationException(反之一样)
- TTL的配置不是检查点或者保存点的一部分,不会存储到检查点或者保存点
3.2 OperatorState
特点:
- 作用在DataStream上;
- 绑定到特定Operator的每个并行度;(一个算子的一个并行度对应一个状态)
- 算子状态不能由别的算子的任务访问。
- 相同算子的不同并行度之间不能互相访问对方的状态
- 算子状态的实际应用场景不如 Keyed State 多,一般用在 Source 或 Sink 等与外部系统连接的算子上, 或者完全没有 key 定义的场景。比 如 Flink 的 Kafka 连接器中,就用到了算子状态。
思考:
一个并行度为3的source有几个状态? (只考虑一个算子需要一个逻辑状态)
答:3个;
算子状态支持的数据类型:
- ListState
- UnionListstate 联合列表状态
将状态表示为数据的列表(集合)。它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复。
联合列表状态在宕机恢复之前,修改代码=>修改并行度,还是可以恢复的;
普通的列表状态相当于每个并行度绑定一个List; - BroadcastState(map)
所有Task上的State都是一样的
算子状态的横向扩展
Operator States的动态扩展是非常灵活的,现提供了3种扩展,下面分别介绍:
- ListState :并发度在改变的时候,会将并发上的每个List都取出,然后把这些List合并到一个新的List,然后根据元素的个数在均匀分配给新的Task。
- UnionListState:相比于ListState更加灵活,把划分的方式交给用户去做,当改变并发的时候,会将原来的List拼接起来。然后不做划分,直接交给用户。
- BroadcastState:如大表和小表做Join时,小表可以直接广播给大表的分区,在每个并发上的数据都是完全一致的。做的更新也相同,当改变并发的时候,把这些数据COPY到新的Task即可。
以上是Flink Operator States提供的3种扩展方式,用户可以根据自己的需求做选择。
算子状态的初始化方法中决定了是哪种类型;如下图所示是UnionListState,那么在程序恢复的时候,自己决定状态的分配
3.3 BroadcastState
如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。【Task的所有并行度的状态都相同】
全局数据用一个状态:
方式1:只用一个并行度(这种因为不能利用并发,所以效率低下)
方式2:用广播状态 并行度1状态更新,并行度2中会同步保存
状态在所有并行度共享
4、状态编程演示
状态编程使用步骤
- 只能在富函数或者ProcessFunction中使用状态编程
- 实现类中声明,但是不初始化
- 在Open方法中初始化
- Process方法或者 元素处理方法中调用
使用注意事项
- 状态默认值为null 所以一般都需要初始化才能做后续计算
- 状态变量作为实现类的属性只能声明不能初始化,因为这是个静态类,类加载的时候运行时上下文是没有的;也不能在处理方法中初始化,否则每一个元素有一个状态了,目的是每个key的数据共享一个状态;在open方法做初始化
4.1 键控状态使用演示
ValueState
wordcount
package No09_state;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
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.util.Collector;
public class _01_wordCount {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//DataStreamSource<String> source = env.socketTextStream("hadoop102", 9999);
DataStreamSource<String> source = env.readTextFile("D:\\IdeaProjects\\bigdata\\flink\\src\\main\\resources\\hello.txt");
SingleOutputStreamOperator<Tuple2<String, Integer>> flat = source.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out) throws Exception {
String[] words = value.split(" ");
for (String word : words) {
out.collect(new Tuple2<>(word, 1));
}
}
});
KeyedStream<Tuple2<String, Integer>, Tuple> key = flat.keyBy(0);
//todo 使用状态编程实现wordCount
SingleOutputStreamOperator<Tuple2<String, Integer>> map = key.map(new RichMapFunction<Tuple2<String, Integer>, Tuple2<String, Integer>>() {
//todo 状态编程 step1. RichFunction 或者 ProcessFunction 实现类
//todo 状态编程 step2. 声明状态变量属性
// 状态变量只能声明 不能初始化,因为类加载的时候还没有运行时上下文
// 也不能在处理方法中初始化,否则每一个元素有一个状态了,目的是每个key的数据共享一个状态
// 在open方法做初始化,这个状态是当前key的stream所共享的
// 默认值为null 所以一般都需要初始化才能做后续计算
//todo 在scala中,添加lazy 懒加载即可
ValueState<Integer> count;
@Override
public void open(Configuration parameters) throws Exception {
//todo 状态编程step3 .open方法在处理从运行时上下文获取上一条数据处理后的状态
count = getRuntimeContext().getState(new ValueStateDescriptor<Integer>(
"word-count", Integer.class, 0
));
//TODO 状态初始化的错误写法写法:
// count = getRuntimeContext().getState(new ValueStateDescriptor<Integer>(
// "word-count", Integer.class));
// count.update(0);
// 报错如下:
// Caused by: java.lang.NullPointerException: No key set. This method should not be called outside of a keyed context.
}
@Override
public Tuple2<String, Integer> map(Tuple2<String, Integer> value) throws Exception {
count.update(count.value() + value.f1);
return new Tuple2<>(value.f0, count.value());
}
});
map.print("wordcount");
env.execute();
}
}
package No09_state;
import Bean.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.Tuple;
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.util.Collector;
public class _02_ValueState {
//实现这样一个需求:检测传感器的温度值,如果连续的两个温度差值超过10度,就输出报警。
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> source = env.socketTextStream("hadoop102", 9999);
KeyedStream<SensorReading, Tuple> sensorReadingTupleKeyedStream = source.map(new MapFunction<String, SensorReading>() {
@Override
public SensorReading map(String s) throws Exception {
String[] fields = s.split(",");
String name = fields[0];
Long ts = Long.parseLong(fields[1]);
double temp = Double.parseDouble(fields[2]);
return new SensorReading(name, ts, temp);
}
})
.keyBy("name");
SingleOutputStreamOperator<String> res = sensorReadingTupleKeyedStream.flatMap(new myProcessFunc());
res.print();
env.execute();
}
//使用flatMap 是因为flatMap不强制输出; map必须要有返回值
public static class myProcessFunc extends RichFlatMapFunction<SensorReading,String>{
ValueState<Double> lastTemp ;
@Override
public void open(Configuration parameters) throws Exception {
lastTemp = getRuntimeContext().getState(new ValueStateDescriptor<Double>("state",Double.class));
}
@Override
public void flatMap(SensorReading sensorReading, Collector<String> collector) throws Exception {
Double last = lastTemp.value();
double now = sensorReading.getTemp();
if(last != null && Math.abs(last - sensorReading.getTemp()) >= 10){
collector.collect(sensorReading.getName() + "上一次温度为" + last + "这一次温度为:" + sensorReading.getTemp());
}
lastTemp.update(now);
}
}
}
重点说明:
生命周期方法和key无关,而是基于slot的,一个并行度的dataflow对应一个生命周期方法,而一个并行度中含有多个key,因此生命周期方法open中只能定义状态,但是不能操作状态!
ListState
package No09_state;
import Bean.SensorReading;
import org.apache.commons.compress.utils.Lists;
import org.apache.flink.api.common.functions.MapFunction;
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.java.tuple.Tuple;
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 java.util.ArrayList;
import java.util.List;
/**
* 每个传感器输出最高的三个温度值
*
*/
public class _03_ListState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(3);
DataStreamSource<String> source = env.socketTextStream("hadoop102", 9999);
KeyedStream<SensorReading, Tuple> keyedStream = source.map(new MapFunction<String, SensorReading>() {
@Override
public SensorReading map(String s) throws Exception {
String[] datas = s.split(",");
return new SensorReading(datas[0], Long.valueOf(datas[1]), Double.valueOf(datas[2]));
}
}).keyBy("name");
//todo 使用ListState实现每个传感器最高的三个水位线
SingleOutputStreamOperator<List<SensorReading>> res = keyedStream.map(new RichMapFunction<SensorReading, List<SensorReading>>() {
//1.定义状态
private ListState<SensorReading> top3;
@Override
public void open(Configuration parameters) throws Exception {
top3 = getRuntimeContext().getListState(new ListStateDescriptor<SensorReading>("list-state", SensorReading.class));
}
@Override
public List<SensorReading> map(SensorReading sensorReading) throws Exception {
top3.add(sensorReading);
ArrayList<SensorReading> sensorReadings = Lists.newArrayList(top3.get().iterator());
sensorReadings.sort((x, y) -> (int) (x.getTemp() - y.getTemp()));
//判断list是否超过三个,如果超过删除最后一条
if (sensorReadings.size() > 3) {
sensorReadings.remove(3);
}
top3.update(sensorReadings);
return sensorReadings;
}
});
res.print();
env.execute();
}
}
ReduceingState
package No09_state;
import Bean.SensorReading;
import akka.stream.impl.ReducerState;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.functions.ReduceFunction;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.state.ReducingState;
import org.apache.flink.api.common.state.ReducingStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
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.environment.StreamExecutionEnvironment;
public class _04_ReduceingState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(3);
DataStreamSource<String> source = env.socketTextStream("hadoop102", 9999);
KeyedStream<SensorReading, Tuple> keyedStream = source.map(new MapFunction<String, SensorReading>() {
@Override
public SensorReading map(String s) throws Exception {
String[] datas = s.split(",");
return new SensorReading(datas[0], Long.valueOf(datas[1]), Double.valueOf(datas[2]));
}
}).keyBy("name");
keyedStream.map(new RichMapFunction<SensorReading, Tuple2<String, Double>>() {
//todo reduceingstate的特点是:只有一个泛型,输入和输出是一样的
ReducingState<SensorReading> reducerState;
@Override
public void open(Configuration parameters) throws Exception {
reducerState = getRuntimeContext().
getReducingState(new ReducingStateDescriptor<SensorReading>(
"reduce-state",
new ReduceFunction<SensorReading>() {
@Override
public SensorReading reduce(SensorReading t1, SensorReading t2) throws Exception {
return new SensorReading(t1.getName(), t1.getTs(), t1.getTemp() + t2.getTemp());
}
}, SensorReading.class
));
}
@Override
public Tuple2<String, Double> map(SensorReading sensorReading) throws Exception {
//聚合
reducerState.add(sensorReading);
SensorReading res = reducerState.get();
return new Tuple2<>(res.getName(), res.getTemp());
}
}).print();
env.execute();
}
}
AggregatingState
package No09_state;
import Bean.SensorReading;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.functions.ReduceFunction;
import org.apache.flink.api.common.functions.RichMapFunction;
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.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
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.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
public class _05_AggregateState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(3);
DataStreamSource<String> source = env.socketTextStream("hadoop102", 9999);
KeyedStream<SensorReading, Tuple> keyedStream = source.map(new MapFunction<String, SensorReading>() {
@Override
public SensorReading map(String s) throws Exception {
String[] datas = s.split(",");
return new SensorReading(datas[0], Long.valueOf(datas[1]), Double.valueOf(datas[2]));
}
}).keyBy("name");
//todo 计算平均水位
keyedStream.process(new KeyedProcessFunction<Tuple,SensorReading, Tuple2<String,Double>>() {
//输入和输出可以不一样
AggregatingState<SensorReading,Double> aggregatingState;
@Override
public void open(Configuration parameters) throws Exception {
aggregatingState = getRuntimeContext().getAggregatingState(
new AggregatingStateDescriptor<SensorReading,Acc, Double>(
"acc-state",
new AggregateFunction<SensorReading, Acc, Double>() {
@Override
public Acc createAccumulator() {
return new Acc(0,0.0);
}
@Override
public Acc add(SensorReading sensorReading, Acc acc) {
return new Acc(acc.getCnt() + 1,acc.getSum() + sensorReading.getTemp());
}
@Override
public Double getResult(Acc acc) {
return acc.getSum()/acc.getCnt();
}
@Override
public Acc merge(Acc acc, Acc acc1) {
return new Acc(acc.getCnt() + acc1.getCnt(),acc.getSum() + acc.getSum());
}
},
Acc.class
)
);
}
@Override
public void processElement(SensorReading value, KeyedProcessFunction<Tuple, SensorReading, Tuple2<String, Double>>.Context ctx, Collector<Tuple2<String, Double>> out) throws Exception {
aggregatingState.add(value);
Double res = aggregatingState.get();
out.collect(new Tuple2<>(value.getName(),res));
}
}).print();
env.execute();
}
public static class Acc{
private Integer cnt;
private Double sum;
public Acc() {
}
public Acc(Integer cnt, Double sum) {
this.cnt = cnt;
this.sum = sum;
}
public Integer getCnt() {
return cnt;
}
public void setCnt(Integer cnt) {
this.cnt = cnt;
}
public Double getSum() {
return sum;
}
public void setSum(Double sum) {
this.sum = sum;
}
}
}
MapState
package No09_state;
import Bean.SensorReading;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.state.AggregatingState;
import org.apache.flink.api.common.state.AggregatingStateDescriptor;
import org.apache.flink.api.common.state.MapState;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
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.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
public class _06_MapState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(3);
DataStreamSource<String> source = env.socketTextStream("hadoop102", 9999);
KeyedStream<SensorReading, Tuple> keyedStream = source.map(new MapFunction<String, SensorReading>() {
@Override
public SensorReading map(String s) throws Exception {
String[] datas = s.split(",");
return new SensorReading(datas[0], Long.valueOf(datas[1]), Double.valueOf(datas[2]));
}
}).keyBy("name");
//todo 重复的不输出
keyedStream.process(new KeyedProcessFunction<Tuple, SensorReading,SensorReading>() {
private MapState<String,String> mapState;
@Override
public void open(Configuration parameters) throws Exception {
mapState = getRuntimeContext()
.getMapState(new MapStateDescriptor< String, String>("map-state",
String.class,String.class));
}
@Override
public void processElement(SensorReading value, KeyedProcessFunction<Tuple, SensorReading, SensorReading>.Context ctx, Collector<SensorReading> out) throws Exception {
//取出
String name = value.getName();
String v = mapState.get(name);
if(v == null){
mapState.put(name,"");
out.collect(value);
}
}
}).print();
env.execute();
}
}
4.2 算子状态使用演示
算子状态两种实现方式
(1)CheckpointedFunction
- 在执行checkpoint的时候,会先调用snapshotState方法,拿到托管状态的值,然后用户自己将原始状态交给托管状态;
- initializeState方法在new Function 实例的时候会被调用,在这一点上和opern方法是一样的;但是此方法提供了一个额外的功能,就是在从checkpoint做程序恢复的时候,会调用一次;此方法是将托管状态转换为原始状态
(2)ListCheckpointedFunction
注意ListCheckpointedFunction和CheckpointedFunction的区别:
- 前者自带一个ListState
- snapshotState方法前者有返回值,就是返回listState,后者没有返回值;
- 前者restoreState方法,说明只需要程序恢复的时候使用,不需要初始化ListState了,后者是initialState方法,需要手动初始化状态以及程序恢复
ListState
package No09_state.算子状态;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
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.environment.StreamExecutionEnvironment;
import java.util.Iterator;
public class _01_ListState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<String> source = env.readTextFile("D:\\IdeaProjects\\bigdata\\flink\\src\\main\\resources\\sensort.txt");
source.map(new myMapFunc()).print();
env.execute();
}
// 算子状态 - listState 实现功能: 统计元素个数
// 算子状态必须要实现CheckpointedFunction 接口
private static class myMapFunc implements MapFunction<String, Integer>,CheckpointedFunction {
private ListState<Integer> listState;
@Override
public Integer map(String line ) throws Exception {
Iterator<Integer> iterator = listState.get().iterator();
if(!iterator.hasNext()){
listState.add(1);
}else{
Integer count = iterator.next();
listState.clear();
listState.add(count + 1);
}
return listState.get().iterator().next();
}
// 初始化状态
@Override
public void initializeState(FunctionInitializationContext context) throws Exception {
listState = context.getOperatorStateStore()
.getListState(new ListStateDescriptor<Integer>(
"list-state",
Integer.class
));
}
//todo 做checkpoint的时候会调用这个方法
@Override
public void snapshotState(FunctionSnapshotContext context) throws Exception {
}
}
}
托管状态和原生状态转换
托管状态
由 Flink 自己来托管,托管有三个方面:
- 在配置容错机制 后,状态会自动持久化保存
- 在发生故障时自动恢复
- 当应用发生 横向扩展时,状态也会自动地重组分配到所有的子任务实例上。
原始状态
用户自定义的一些数据结构,flink不会保管,只在内存中,因此需要在做快照或者状态恢复的时候需要自己处理;
package No09_state.算子状态;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
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.environment.StreamExecutionEnvironment;
import java.util.Iterator;
public class _01_ListState_原始状态和托管状态互换 {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<String> source = env.readTextFile("D:\\IdeaProjects\\bigdata\\flink\\src\\main\\resources\\sensort.txt");
source.map(new myMapFunc()).print();
env.execute();
}
// 算子状态 - listState 实现功能: 统计元素个数
// 算子状态必须要实现CheckpointedFunction 接口
private static class myMapFunc implements MapFunction<String, Integer>,CheckpointedFunction {
private ListState<Integer> listState;//托管状态
int count; //原始状态
@Override
public Integer map(String line ) throws Exception {
count ++;
return count;
}
// 初始化状态的方法会有两次调用时机:
// 1.第一次创建
// 2.程序从checkpoint恢复
@Override
public void initializeState(FunctionInitializationContext context) throws Exception {
// todo 初始化状态
listState = context.getOperatorStateStore()
.getListState(new ListStateDescriptor<Integer>(
"list-state",
Integer.class
));
// todo 2.context.isRestored() => true 表示 状态恢复
// 状态恢复的时候 状态不为空 将托管状态交给原始状态
if(context.isRestored()){
Iterable<Integer> integers = listState.get();
count = integers.iterator().next();
}
}
//todo 做checkpoint的时候会调用这个方法
// 将原始状态 转换成 托管状态
@Override
public void snapshotState(FunctionSnapshotContext context) throws Exception {
listState.clear();
listState.add(count);
}
}
}
4.3 BroadCastState
- 1.5 版本引入
- 广播状态:来自一个流的一些数据需要广播到下游任务,在下游被本地存储,并用于处理另一个流上的所有传入元素。作为广播状态自然适合出现的一个例子:一个底吞吐量流,其中包含一组规则,我们希望根据来自另一个流的所有元素对这些规则进行评估。考虑到上述类型的用例,广播状态和其他算子状态的区别:
- map格式
- 只对输入有广播流和无广播流的特定操作符可用
- 这样的操作符可以具有不同名称的多个广播状态
package No09_state.算子状态;
import org.apache.flink.api.common.state.BroadcastState;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.api.common.state.ReadOnlyBroadcastState;
import org.apache.flink.streaming.api.datastream.BroadcastConnectedStream;
import org.apache.flink.streaming.api.datastream.BroadcastStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.co.BroadcastProcessFunction;
import org.apache.flink.util.Collector;
public class _02_BroadCastState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
//todo 1.两个流
DataStreamSource<String> source1 = env.socketTextStream("hadoop102", 9999);
DataStreamSource<String> source2 = env.socketTextStream("hadoop102", 8888);
//todo 2.定义状态并广播
MapStateDescriptor<String, String> mapStateDescriptor = new MapStateDescriptor<>(
"map-state",
String.class,
String.class
);
//广播出去
BroadcastStream<String> broadcast = source1.broadcast(mapStateDescriptor);
//todo 3.连接数据 和 广播流 构成: BroadcastConnectedStream
BroadcastConnectedStream<String, String> connect = source2.connect(broadcast);
//todo 4.处理连接后的流
connect.process(new BroadcastProcessFunction<String, String, String>() {
@Override
public void processElement(String s, ReadOnlyContext readOnlyContext, Collector<String> collector) throws Exception {
// 获取广播状态 获取另一条流的数据
ReadOnlyBroadcastState<String, String> broadcastState = readOnlyContext.getBroadcastState(mapStateDescriptor);
String aSwitch = broadcastState.get("switch");
if("1".equals(aSwitch)){
collector.collect("读取了广播状态,切换1");
}else if("2".equals(aSwitch)){
collector.collect("读取了广播状态,切换2");
}else if("3".equals(aSwitch)){
collector.collect("读取了广播状态,切换3");
}
}
@Override
public void processBroadcastElement(String s, Context context, Collector<String> collector) throws Exception {
BroadcastState<String, String> broadcastState = context.getBroadcastState(mapStateDescriptor);
//处理广播流的输入数据,放进广播状态中
broadcastState.put("switch",s);
}
}).print();
env.execute();
}
//端口8888 输入1
//打印结果:
//3> 读取了广播状态,切换1
//2> 读取了广播状态,切换1
//1> 读取了广播状态,切换1
//说明当前流每个并行度都能读取到广播状态
}