【FLink-13-Flink 状态State相关概念】

本文介绍了Flink中的状态管理,包括RowState和FlinkState的区别,以及OperatorState和KeyedState的使用。KeyedState每个key维护独立状态空间,适合键控操作,而OperatorState主要用于source端记录偏移量。文章还讨论了Flink在job宕机重启时如何处理状态数据的重分配,并提供了使用不同状态类型的代码示例。
摘要由CSDN通过智能技术生成

Flink状态State的相关概念

在稍复杂的流式计算逻辑中,基本都需要记录和利用一些历史累积信息;
例如,对整数流进行全局累计求和的逻辑:

  • 1 我们在算子中定义一个变量来记录累计到当前的总和;
  • 2 在一条新数据到达时,就将新数据累加到总和变量中;
  • 3 然后输出结果;
    由上可知:状态就是用户在程序逻辑中用于记录信息的变量 (当然,依据不同的需求,状态数据可多可少,可简单可复杂)!
    如上所述,state 只不过是用户编程时自定义的“变量”,跟 flink 又有何关系呢? 因为状态(状态中记录的数据),需要容错!!!程序一旦在运行中突然失败,则用户自定义的状态 所记录的数据会丢失,因而无法实现失败后重启的接续!

1.Row State和 Flink State

  • Row State(自管理状态):自己定义、自己管理的状态,这种状态叫做自管理状态,自管理状态的持久化和容错都很困难、
  • Flink State(托管状态):需要Flink来帮忙管理状态数据,不需要自己定义一个变量,而是要从flink的api中去获取一个状态管理器,用这个状态管理器来进行数据的增删改查等操作。
    • 使用Flink state的话,对数据进行容错,必须要开启 快照机制checkpoint(持久化)

    • 快照机制,每次只存储一个最新的状态数据,且会覆盖之前的状态数据,之前的状态数据都会被删除掉。

    • flink中task失败的时候,会有自动重启机制,即 failover机制(失败切换);跟job失败是两种维度,job失败是程序运行有Bug导致;开启failover机制的相关代码如下:
      在这里插入图片描述

      • 如果使用固定重启策略的话,不管哪个task,总共出现三次异常的话,就会直接抛出异常,程序停止运行。
    • 在task失败后,task自动重启时,会帮用户自动加载最近一次的快照状态,而job失败重启后,不会自动加载失败之前的快照数据,需要我们手动指定加载最近一次的快照状态的位置。

2.算子状态(Operator State)和 键控状态(Keyed State)

  • flink中state分为Operator State 和 Keyed State
    • 使用operator state ,需要让用户自己写的Function类去实现CheckpointedFunction算子,然后在其中的方法initializeState中,去拿到Operator State存储器。
    • 使用keyed state的时候,需要使用Rich类型的方法,这样的话,就不需要实现类似Operator state中的方法了,并且实现了Rich方法的话,可以调用open()方法。
    • 并且使用keyed state的时候,每个key维护一个自己状态空间,且不同key之间状态空间不一致,如下图所示:上部分是键控状态,abcd各有一个自己的状态空间;下部分是算子状态,数据之间使用rebalance策略来轮询写出数据到下游。

在这里插入图片描述

  • 相关代码案例如下:
    在这里插入图片描述

3.Flink的job宕机重启时,算子状态和键控状态对应的快照数据重分配问题

1.算子状态Operator State重分配

  • 如果使用算子状态Operator State的话,一般都是在source端使用,用来记录偏移量,记录偏移量的话,就不会出现关于数据逻辑的问题,只会将偏移量分配给不同分区,而不进行数据相关信息的传递,如果是在下面的算子中使用算子状态的话,会有数据参与到算子状态中记录,重启会出现数据逻辑的混乱问题;
  • 综上:算子状态建议在source端使用,不建议在算子内部使用。
    • 1.程序正常运行情况下的状态数据
      在这里插入图片描述
    • 2.flink的job宕机重启后,并行度可能会发生变化,可能会增加或者减少,然后对应的状态数据需要重新分配,如下图所示
      在这里插入图片描述

2.键控状态Keyed State重分配

  • Keyed State不存在算子状态中数据逻辑这样的问题,这中间不会出现数据参与运算,只会使用关于keyed中记录的状态信息,但是也会出现状态重分配的情况。
  • 使用keyed State时,接收数据时,数据的分发规律 和 加载以前checkpoint数据时的数据分配规律是一致的,所以重新分配不会造成任何逻辑影响。
    在这里插入图片描述

3.Flink的数据结构状态管理器API

  • 1.Flink的数据结构状态管理器包括:

    • 单值状态
    • list状态
    • map状态
    • reducing状态
    • aggregate状态
  • 2.相关代码案例如下:

package com.yang.flink.state;

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.ReduceFunction;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.functions.RuntimeContext;
import org.apache.flink.api.common.restartstrategy.RestartStrategies;
import org.apache.flink.api.common.state.*;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.CheckpointingMode;
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.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 键控状态使用演示
 */
public class StateKeyedStateDemo {
    public static void main(String[] args) {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        env.setRuntimeMode(RuntimeExecutionMode.STREAMING);

        // 开启状态数据的checkpoint机制(快照的周期,快照的模式)
        env.enableCheckpointing(1000, CheckpointingMode.EXACTLY_ONCE);

        // 开启快照后,就需要指定快照数据的持久化存储位置
        env.getCheckpointConfig().setCheckpointStorage("file:///d:/checkpoint/");


        // 开启  task级别故障自动 failover
        env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3,1000));


        DataStreamSource<String> source = env.socketTextStream("localhost", 9999);

        // 需要使用map算子来达到一个效果:
        // 每来一条数据(字符串),输出 该条字符串拼接此前到达过的所有字符串
        SingleOutputStreamOperator<String> mapOperator = source.keyBy(s -> s)
                .map(new RichMapFunction<String, String>() {//使用map算子时候,键控状态算子,需要实现rich的function,获取到flink的状态

                    // 单值状态
                    ValueState<String> valueState;
                    // list状态
                    ListState<String> lstState;
                    // map状态
                    MapState<String, String> mapState;
                    // reducing状态
                    ReducingState<Integer> reduceState;
                    // aggregate状态
                    AggregatingState<Integer, Double> aggState;

                    @Override
                    public void open(Configuration parameters) throws Exception {
                        RuntimeContext runtimeContext = getRuntimeContext();

                        // 获取一个 单值 结构的状态存储器
                        valueState = runtimeContext.getState(new ValueStateDescriptor<String>("vstate", String.class));


                        // 获取一个List结构的状态存储器
                        lstState = runtimeContext.getListState(new ListStateDescriptor<String>("lst", String.class));



                        // 获取一个 Map 结构的状态存储器
                        mapState = runtimeContext.getMapState(new MapStateDescriptor<String, String>("xx", String.class, String.class));


                        // 获取一个reduce聚合状态
                        reduceState = runtimeContext.getReducingState(new ReducingStateDescriptor<Integer>("reduceState", new ReduceFunction<Integer>() {
                            @Override
                            public Integer reduce(Integer value1, Integer value2) throws Exception {
                                return value1 + value2;
                            }
                        }, Integer.class));


                        // 获取一个aggregate聚合状态
                        // 比如,我们要插入整数,返回平均值
                        aggState = runtimeContext.getAggregatingState(new AggregatingStateDescriptor<Integer, Tuple2<Integer, Integer>, Double>("aggState", new AggregateFunction<Integer, Tuple2<Integer, Integer>, Double>() {
                            @Override
                            public Tuple2<Integer, Integer> createAccumulator() {
                                return Tuple2.of(0, 0);
                            }

                            @Override
                            public Tuple2<Integer, Integer> add(Integer value, Tuple2<Integer, Integer> accumulator) {
                                return Tuple2.of(accumulator.f0 + 1, accumulator.f1 + value);
                            }

                            @Override
                            public Double getResult(Tuple2<Integer, Integer> accumulator) {

                                return accumulator.f1 / (double) accumulator.f0;
                            }

                            @Override
                            public Tuple2<Integer, Integer> merge(Tuple2<Integer, Integer> a, Tuple2<Integer, Integer> b) {

                                return Tuple2.of(a.f0 + b.f0, a.f1 + a.f1);
                            }

                        }, TypeInformation.of(new TypeHint<Tuple2<Integer, Integer>>() {
                        })));


                    }

                    @Override
                    public String map(String value) throws Exception {
                        /**
                         * ValueState的数据操作api
                         */
                        valueState.update("xxx");  // 更新掉状态中的值
                        String str = valueState.value();  // 获取状态中的值


                        /**
                         * listState的数据操作api
                         */
                        Iterable<String> strings = lstState.get(); // 拿到整个liststate的数据迭代器

                        lstState.add("a"); // 添加一个元素到liststate中

                        lstState.addAll(Arrays.asList("b","c","d"));  // 一次性放入多个元素到liststate中

                        lstState.update(Arrays.asList("1","2","3")); // 一次性将liststate中的数据替换为传入的元素

                        /**
                         * mapState的数据操作api
                         */
                        String v = mapState.get("a");  // 从mapstate中根据一个key来获取它的value

                        boolean contain = mapState.contains("a");  // 判断mapstate中是否包含指定的key

                        Iterator<Map.Entry<String, String>> entryIterator = mapState.iterator(); // 拿到mapstate的entry迭代器
                        Iterable<Map.Entry<String, String>> entryIterable = mapState.entries(); // 拿到mapstate的entry的 Iterable(内含迭代器)

                        mapState.put("a","100");  // 往mapstate中插入一对KV

                        boolean isEmpty = mapState.isEmpty();  // 判断mapstate中是否没有元素(是否为空)

                        HashMap<String, String> dataMap = new HashMap<>();
                        dataMap.put("a","1");
                        dataMap.put("b","2");

                        mapState.putAll(dataMap);  // 通过一个hashmap对象,来一次性放入多对KV到mapstate中

                        Iterable<String> keys = mapState.keys();  // 拿到mapstate中所有key
                        Iterable<String> values = mapState.values(); // 拿到mapstate中的所有value

                        mapState.remove("a"); // 从mapstate移除key=“a"的条目

                        /**
                         * reduce 状态使用
                         */
                        reduceState.add(10);  // 往聚合状态中添加数据  ,此刻状态中的数据是10
                        reduceState.add(20);  // 往聚合状态中添加数据  ,此刻状态中的数据是30
                        Integer stateValue = reduceState.get();  // 从聚合状态中获取数据值


                        /**
                         * aggreate 状态使用
                         */
                        aggState.add(10);
                        aggState.add(20);

                        Double avgDouble = aggState.get(); // 获取状态值 : 15.0
                        return null;
                    }
                });
        mapOperator.print();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值