【Flink流式计算框架】State(KeyedState/OperatorState)和StateBackend及配置

State:一般指一个具体的task/operator的状态。
     Keyed State(keyBy):托管状态、原始状态 工作中用的较多
     Operator State(unKeyBy):托管状态、原始状态 工作中用的较少
     原始状态工作中基本用不到
Keyed State六种托管状态
     Flatmap正常情况下实现FlatMapFunction然后重写flatMap<>方法,为了更丰富的功能另一种方式是继承RichFlatMapFunction<IN,OUT> ,多了一个初始化方法open只会运行一次。用Rich目的就是用来注册状态
     ValueState保存的是对应的一个key的一个状态值
在这里插入图片描述
     在flatMap中对上面的countAndSum进行业务逻辑操作
     SparkStreaming中updateStateByKey、mapWithState管理状态有限

     ListState保存的是对应的一个key出现的所有的元素
     MapState:Map数据结构,key相同会覆盖value的值。将map转换成list进行操作Lists.newArrayList(mapState.value());

     利用flatMap/map+state自定义出功能丰富的算子

State

state概述

state:一般指一个具体的task/operator的状态。State可以被记录,在失败的情况下数据还可以恢复,Flink中有两种基本类型的State:Keyed State,Operator State,他们两种都可以以两种形式存在:原始状态(raw state)和托管状态(managed state)
托管状态:由Flink框架管理的状态,我们通常使用的就是这种。
原始状态:由用户自行管理状态具体的数据结构,框架在做checkpoint的时候,使用byte[]来读写状态内容,对其内部数据结构一无所知。通常在DataStream上的状态推荐使用托管的状态,当实现一个用户自定义的operator时,会使用到原始状态。但是我们工作中一般不常用,所以我们不考虑他。

用一个简单的例子,来说明flink状态state的具体存在:

/**
 * 单词计数
 */
public class WordCount {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> data = env.socketTextStream("localhost", 8888);
        SingleOutputStreamOperator<Tuple2<String, Integer>> result = data.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String line, Collector<Tuple2<String, Integer>> collector) throws Exception {
                String[] fields = line.split(",");
                for (String word : fields) {
                    collector.collect(new Tuple2<>(word, 1));
                }
            }
        }).keyBy("0")
                .sum(1);

        result.print();
        env.execute("WordCount");
    }
}
#输入
hadoop,hadoop
hadoop
hive,hadoop 
#输出
4> (hadoop,1)
4> (hadoop,2)
4> (hadoop,3)	
1> (hive,1)
4> (hadoop,4)

单词出现的次数有累计的效果。如果没有状态的管理,是不会有累计的效果的,所以Flink里面是有state概念的。

State类型

Operator State

  1. operator state是task级别的state,就是每个task对应一个state
  2. Kafka Connector source中的每个分区(task)都需要记录消费的topic的partition和offset等信息。
  3. operator state 只有一种托管状态: ListState

Keyed State

  1. keyed state 记录的是每个key的状态
  2. Keyed state托管状态有六种类型:
    1. ValueState
    2. ListState
    3. MapState
    4. ReducingState
    5. AggregatingState
    6. FoldingState
dataStreamSource
   .keyBy(0)
   .flatMap(new CountWindowAverageWithValueState())
   //.flatMap(new CountWindowAverageWithListState())
   //.flatMap(new CountWindowAverageWithMapState())
   //.flatMap(new SumFunction())
   //.flatMap(new ContainsValueFunction())
   .print();

自定义flatMap方法继承RichFlatMapFunction< T >,然后重写open方法,flatMap方法。
在open方法中注册状态:
一是状态描述,二是从上下文获取状态

ValueStateDescriptor<Tuple2<Long, Long>> descriptor =
    new ValueStateDescriptor<Tuple2<Long, Long>>(
            "average",  // 状态的名字
            Types.TUPLE(Types.LONG, Types.LONG)); // 状态存储的数据类型
countAndSum = getRuntimeContext().getState(descriptor);

ListStateDescriptor<Tuple2<Long, Long>> descriptor =
	new ListStateDescriptor<Tuple2<Long, Long>>(
	       "average",  // 状态的名字
	       Types.TUPLE(Types.LONG, Types.LONG)); // 状态存储的数据类型
elementsByKey = getRuntimeContext().getListState(descriptor);

MapStateDescriptor<String, Long> descriptor =
    new MapStateDescriptor<String, Long>(
             "average",  // 状态的名字
             String.class, Long.class); // 状态存储的数据类型
mapState = getRuntimeContext().getMapState(descriptor);

ReducingStateDescriptor<Long> descriptor =
    new ReducingStateDescriptor<Long>(
            "sum",  // 状态的名字
            new ReduceFunction<Long>() { // 聚合函数
                @Override
                public Long reduce(Long value1, Long value2) throws Exception {
                    return value1 + value2;
                }
            }, Long.class); // 状态存储的数据类型
sumState = getRuntimeContext().getReducingState(descriptor);

AggregatingStateDescriptor<Long, String, String> descriptor =
    new AggregatingStateDescriptor<Long, String, String>(
             "totalStr",  // 状态的名字
             new AggregateFunction<Long, String, String>() {
                 @Override
                 public String createAccumulator() {
                     return "Contains:";
                 }

                 @Override
                 public String add(Long value, String accumulator) {
                     if ("Contains:".equals(accumulator)) {
                         return accumulator + value;
                     }
                     return accumulator + " and " + value;
                 }

                 @Override
                 public String getResult(String accumulator) {
                     return accumulator;
                 }

                 @Override
                 public String merge(String a, String b) {
                     return a + " and " + b;
                 }
             }, String.class); // 状态存储的数据类型
totalStr = getRuntimeContext().getAggregatingState(descriptor);

在flatmap方法内,做状态操作

ValueState :这个状态为每一个 key 保存一个值
value() 获取状态值
update() 更新状态值
clear() 清除状态

ListState :这个状态为每一个 key 保存集合的值
get() 获取状态值
add() / addAll() 更新状态值,将数据放到状态中
clear() 清除状态

MapState<K, V> :这个状态为每一个 key 保存一个 Map 集合
put() 将对应的 key 的键值对放到状态中
values() 拿到 MapState 中所有的 value
clear() 清除状态

ReducingState :这个状态为每一个 key 保存一个聚合之后的值
get() 获取状态值
add() 更新状态值,将数据放到状态中
clear() 清除状态

KeyedState案例

需求:将两个流中,订单号一样的数据合并在一起输出

public class OrderStream {
    public static void main(String[] args) throws  Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> info1 = env.addSource(new FileSource(Constants.ORDER_INFO1_PATH));
        DataStreamSource<String> info2 = env.addSource(new FileSource(Constants.ORDER_INFO2_PATH));

        KeyedStream<OrderInfo1, Long> orderInfo1Stream = info1.map(line -> string2OrderInfo1(line))
                .keyBy(orderInfo1 -> orderInfo1.getOrderId());

        KeyedStream<OrderInfo2, Long> orderInfo2Stream = info2.map(line -> string2OrderInfo2(line))
                .keyBy(orderInfo2 -> orderInfo2.getOrderId());

        orderInfo1Stream.connect(orderInfo2Stream)
                .flatMap(new EnrichmentFunction())
                .print();

        env.execute("OrderStream");

    }

    /**
     *   IN1, 第一个流的输入的数据类型 
         IN2, 第二个流的输入的数据类型
         OUT,输出的数据类型
     */
    public static class EnrichmentFunction extends
            RichCoFlatMapFunction<OrderInfo1,OrderInfo2,Tuple2<OrderInfo1,OrderInfo2>>{
		//定义第一个流 key对应的state
        private ValueState<OrderInfo1> orderInfo1State;
        //定义第二个流 key对应的state
        private ValueState<OrderInfo2> orderInfo2State;

        @Override
        public void open(Configuration parameters) {
            orderInfo1State = getRuntimeContext()
                    .getState(new ValueStateDescriptor<OrderInfo1>("info1", OrderInfo1.class));
            orderInfo2State = getRuntimeContext()
                    .getState(new ValueStateDescriptor<OrderInfo2>("info2",OrderInfo2.class));

        }

        @Override
        public void flatMap1(OrderInfo1 orderInfo1, Collector<Tuple2<OrderInfo1, OrderInfo2>> out) throws Exception {
            OrderInfo2 value2 = orderInfo2State.value();
            if(value2 != null){
                orderInfo2State.clear();
                out.collect(Tuple2.of(orderInfo1,value2));
            }else{
                orderInfo1State.update(orderInfo1);
            }

        }

        @Override
        public void flatMap2(OrderInfo2 orderInfo2, Collector<Tuple2<OrderInfo1, OrderInfo2>> out)throws Exception {
            OrderInfo1 value1 = orderInfo1State.value();
            if(value1 != null){
                orderInfo1State.clear();
                out.collect(Tuple2.of(value1,orderInfo2));
            }else{
                orderInfo2State.update(orderInfo2);
            }
        }
    }
}

这里的flatmap1,相同orderId才会放在一起,就是orderInfo1数据过来了,orderInfo2数据还没过,就把orderInfo1放入状态中,等到orderInfo2数据过来了,查看相同orderId下的orderInfo1状态是有值,有,则组合输出;这里的flatmap2,和flatmap1同样的道理。

Operator State案例(unKeyedState)

需求: 每两条数据打印一次结果

public class CustomSink
        implements SinkFunction<Tuple2<String, Integer>>, CheckpointedFunction {
    /**
     *
     *  数据是不安全。添加上CheckpointedRunction数据更安全
     *
     *  现在只是要求,每2条数据打印一次。
     *  如果改一下需求,每1000条打印一次。
     *  checkpoint  -》 999 保存了 1
     *
     *  999 -》 突然直接,程序宕机了,那这999条数据会丢失
     */

    // 用于缓存结果数据的, 内存  Java  堆内存
    private List<Tuple2<String, Integer>> bufferElements;
    // 表示内存中数据的大小阈值 / 2
    private int threshold;
    // 用于保存内存中的状态信息
    //ListState:磁盘,内存,?
    private ListState<Tuple2<String, Integer>> checkpointState;
    // StateBackend
    // checkpoint

    public CustomSink(int threshold) {
        this.threshold = threshold;
        this.bufferElements = new ArrayList<>();
    }
    
    @Override
    public void invoke(Tuple2<String, Integer> value, Context context) throws Exception {
        // 可以将接收到的每一条数据保存到任何的存储系统中
        bufferElements.add(value);
        if (bufferElements.size() == threshold) {//2
            // 简单打印
            System.out.println("自定义格式:" + bufferElements);
            bufferElements.clear();
        }
    }

    // 用于将内存中数据保存到状态中
    @Override
    public void snapshotState(FunctionSnapshotContext context) throws Exception {
        checkpointState.clear();
        for (Tuple2<String, Integer> ele : bufferElements) {
            checkpointState.add(ele);
        }
    }
    // 用于在程序挥发的时候从状态中恢复数据到内存

    //open 初始化,只会被调用一次。
    @Override
    public void initializeState(FunctionInitializationContext context) throws Exception {

        ListStateDescriptor<Tuple2<String, Integer>> descriptor =
                new ListStateDescriptor<Tuple2<String, Integer>>(
                        "bufferd -elements",
                        TypeInformation.of(new TypeHint<Tuple2<String, Integer>>() {}));

        // 注册一个 operator state
        checkpointState = context.getOperatorStateStore().getListState(descriptor);

        if (context.isRestored()) {
            for (Tuple2<String, Integer> ele : checkpointState.get()) {
                //内存
                bufferElements.add(ele);
            }
        }
    }
}
public class TestOperatorStateMain {
    public static void main(String[] args) throws  Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStreamSource<Tuple2<String, Integer>> dataStreamSource =
                env.fromElements(Tuple2.of("Spark", 3), Tuple2.of("Hadoop", 5), Tuple2.of("Hadoop", 7),
                        Tuple2.of("Spark", 4));
        dataStreamSource
                //print只能一条数据一条数据的打印
                //超级print的算子
                .addSink(new CustomSink(2)).setParallelism(1);

        env.execute("TestStatefulApi");
    }
}

State backend

Flink支持的StateBackend:

  • MemoryStateBackend
  • FsStateBackend
  • RocksDBStateBackend

MemoryStateBackend

默认情况下,状态信息是存储在 TaskManager 的堆内存中的,checkpoint 的时候将状态保存到 JobManager 的堆内存中。

缺点:
​ 只能保存数据量小的状态
​ 状态数据有可能会丢失
优点:
​ 开发测试很方便

FsStateBackend

状态信息存储在 TaskManager 的堆内存中的,checkpoint 的时候将状态保存到指定的文件中 (HDFS 等文件系统)

缺点:
状态大小受TaskManager内存限制(默认支持5M)
优点:
状态访问速度很快
状态信息不会丢失
用于: 生产,也可存储状态数据量大的情况

RocksDBStateBackend

状态信息存储在 RocksDB 数据库 (key-value 的数据存储服务), 最终保存在本地文件中checkpoint 的时候将状态保存到指定的文件中 (HDFS 等文件系统),需要引入jar包,

缺点:
状态访问速度有所下降
优点:
可以存储超大量的状态信息
状态信息不会丢失
用于: 生产,可以存储超大量的状态信息

StateBackend配置方式

(1)单任务调整
修改当前任务代码
env.setStateBackend(new FsStateBackend(“hdfs://namenode:9000/flink/checkpoints”));
或者new MemoryStateBackend()
或者new RocksDBStateBackend(filebackend, true);【需要添加第三方依赖】
在这里插入图片描述

(2)全局调整
修改flink-conf.yaml
state.backend: filesystem
state.checkpoints.dir: hdfs://namenode:9000/flink/checkpoints
注意:state.backend的值可以是下面几种:jobmanager(MemoryStateBackend), filesystem(FsStateBackend), rocksdb(RocksDBStateBackend)

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页