【Flink】【第9章】【 状态管理和容错机制】【01】状态编程

参考文章

Apache Flink状态管理和容错机制介绍


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 托管状态的数据结构图

在这里插入图片描述

  1. ValueState:存一个value
  2. ListState:存多个value
  3. MapState:存多个key-value类型数据
  4. ReducingState:用于聚合
  5. FoldingState:用于聚合
  6. 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、状态编程演示

状态编程使用步骤

  1. 只能在富函数或者ProcessFunction中使用状态编程
  2. 实现类中声明,但是不初始化
  3. 在Open方法中初始化
  4. Process方法或者 元素处理方法中调用

使用注意事项

  1. 状态默认值为null 所以一般都需要初始化才能做后续计算
  2. 状态变量作为实现类的属性只能声明不能初始化,因为这是个静态类,类加载的时候运行时上下文是没有的;也不能在处理方法中初始化,否则每一个元素有一个状态了,目的是每个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

    //说明当前流每个并行度都能读取到广播状态
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值