Flink状态管理

本文介绍了ApacheFlink中五种关键的状态管理方式:值状态、列表状态、映射状态、归约状态和聚合状态,以及状态生存时间和状态后端的选择,以实现在流处理中的状态持久化和计算。
摘要由CSDN通过智能技术生成

目录

值状态(ValueState)

列表状态(ListState)

Map状态(MapState)

归约状态(ReducingState)

聚合状态(AggregatingState)

广播状态(BroadcastState)

状态生存时间(TTL)

状态后端


值状态(ValueState)

        Flink状态保留一个值,例如Integer、Long类型。

        常用的方法如下

value()  // 获取当前状态的值
update() // 更新当前状态的值

        下面实现一个简单案例,来加深理解。watersensor对象的 ts 表示水位值,现在需要找到连续两条水位值相差大于10的数据,这时候就可以用值状态来存储上一条水位值。

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("node1", 7777)
                .map(new WaterSensorMapFunction())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner((value,ts) -> value.getTs()*1000L)
                );

        sensorDS
                .keyBy(value -> value.getId())
                .process(new KeyedProcessFunction<String, WaterSensor, String>() {

                    // TODO 1.定义状态
                    ValueState<Integer>  lastValueState;

                    @Override
                    public void open(Configuration parameters) throws Exception {
                        super.open(parameters);
                        // TODO 2.在open方法中,初始化状态
                        // 状态描述器两个参数:第一个参数,起个名字,不重复;第二个参数,存储的类型
                        lastValueState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("valueState", Types.INT));
                    }

                    @Override
                    public void processElement(WaterSensor waterSensor, Context context, Collector<String> collector) throws Exception {

                        // 取出上一条水位数据(存储在值状态中)
                        int lastVc = lastValueState.value() == null ? 0 : lastValueState.value();
                        Integer vc = waterSensor.vc;
                        if(Math.abs(lastVc-vc) > 10){
                            collector.collect("传感器:" + context.getCurrentKey() + "当前水位值" + vc + ",上一条水位值" + lastVc + "相差超过10");
                        }

                        lastValueState.update(vc);

                    }
                })
                .print();

        env.execute();
    }

        由于使用按键分区状态,可以看到,s2,11,11这条数据,他的上一条tc应该为null,值状态也为空,代码中手动赋值为0,因此这条数据也被打印输出。因此,也证明了每一个key都有一个状态。

列表状态(ListState)

        将需要保存的值,以列表的形式存储起来。

        常用方法如下

ListState.get();            //取出 list状态 本组的数据,是一个Iterable
ListState.add();            // 向 list状态 本组 添加一个元素
ListState.addAll();         // 向 list状态 本组 添加多个元素
ListState.update();         // 更新 list状态 本组数据(覆盖)
ListState.clear();          // 清空List状态 本组数据

        下面实现一个案例,输出每个传感器(id)最高的三个水位值。

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("node1", 7777)
                .map(new WaterSensorMapFunction())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner((value,ts) -> value.getTs()*1000L)
                );

        // 输出每个传感器最高的3个水位值
        sensorDS.keyBy(value -> value.getId())
                .process(new KeyedProcessFunction<String, WaterSensor, String>() {

                    ListState<Integer> listState;

                    @Override
                    public void open(Configuration parameters) throws Exception {
                        super.open(parameters);
                        listState = getRuntimeContext().getListState(new ListStateDescriptor("vclistState", Types.INT));
                    }

                    @Override
                    public void processElement(WaterSensor waterSensor, Context context, Collector<String> collector) throws Exception {

                        // 1、来一条数据存储一条
                        listState.add(waterSensor.vc);

                        // 2、从list状态拿出来(Iterable), 拷贝到一个List中,排序, 只留3个最大的
                        Iterable<Integer> iter = listState.get();
                        List<Integer> list = new ArrayList<>();
                        for (Integer vc : iter) {
                            list.add(vc);
                        }
                        // 降序,后 - 前
                        list.sort((o1,o2) -> (o2-o1));

                        // 3、只保留最大的3个(list中的个数一定是连续变大,一超过3就立即清理即可)
                        if(list.size() > 3){
                            list.remove(3);
                        }
                        collector.collect("传感器id为" + waterSensor.getId() + "最大的三个水位值为" + list.toString());

                        listState.update(list);
                    }
                })
                .print();
        
        env.execute();
    }

Map状态(MapState)

        将状态以键值对的形式存储。

        常用方法如下:

        vcCountMapState.get();          // 对本组的Map状态,根据key,获取value
        vcCountMapState.contains();     // 对本组的Map状态,判断key是否存在
        vcCountMapState.put(, );        // 对本组的Map状态,添加一个 键值对
        vcCountMapState.putAll();  // 对本组的Map状态,添加多个 键值对
        vcCountMapState.entries();      // 对本组的Map状态,获取所有键值对
        vcCountMapState.keys();         // 对本组的Map状态,获取所有键
        vcCountMapState.values();       // 对本组的Map状态,获取所有值
        vcCountMapState.remove();   // 对本组的Map状态,根据指定key,移除键值对
        vcCountMapState.isEmpty();      // 对本组的Map状态,判断是否为空
        vcCountMapState.iterator();     // 对本组的Map状态,获取迭代器
        vcCountMapState.clear();        // 对本组的Map状态,清空

        实现一个具体的案例,统计每种传感器不同水位线出现的次数

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("node1", 7777)
                .map(new WaterSensorMapFunction())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner((value,ts) -> value.getTs()*1000L)
                );

        sensorDS
                .keyBy(value -> value.getId())
                .process(new KeyedProcessFunction<String, WaterSensor, String>() {

                    // Map<vc,count>
                    MapState<Integer,Integer> mapState;

                    @Override
                    public void open(Configuration parameters) throws Exception {
                        super.open(parameters);
                        mapState = getRuntimeContext().getMapState(new MapStateDescriptor<Integer, Integer>(
                                "mapState",
                                Types.INT,
                                Types.INT
                        ));
                    }

                    @Override
                    public void processElement(WaterSensor waterSensor, Context context, Collector<String> collector) throws Exception {

                        // 判断是否存在key
                        Integer vc = waterSensor.getVc();
                        if(mapState.contains(vc)){
                            // 存在,则修改count值
                            Integer count = mapState.get(vc);
                            mapState.put(vc,++count);
                        }else{
                            // 不存在,则放入mapState
                            mapState.put(vc,1);
                        }

                        // 遍历Map状态,输出每个k-v的值
                        StringBuilder result = new StringBuilder();
                        result.append("================\n");
                        result.append("传感器id为" + waterSensor.getId() + "\n");
                        for (Map.Entry<Integer, Integer> entry : mapState.entries()) {
                            // entries 返回完整的键值对数据,可以直接toString
                            result.append(entry.toString() + "\n");
                        }

                        collector.collect(result.toString());
                    }
                })
                .print();

        env.execute();

    }

归约状态(ReducingState)

        类似值状态,对于添加进来的所有数据进行规约,结果作为规约状态保存起来。调用add方法时,不是将数据存储到状态中,而是将规约计算的结果存储到状态里。

        常用方法如下:

        vcSumReducingState.get();   // 对本组的Reducing状态,获取结果
        vcSumReducingState.add();   // 对本组的Reducing状态,添加数据
        vcSumReducingState.clear(); // 对本组的Reducing状态,清空数据

        实现案例:计算每种传感器的水位和

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("node1", 7777)
                .map(new WaterSensorMapFunction())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner((value,ts) -> value.getTs()*1000L)
                );

        sensorDS
                .keyBy(value -> value.getId())
                .process(new KeyedProcessFunction<String, WaterSensor, String>() {

                    ReducingState<Integer> reducingState;

                    @Override
                    public void open(Configuration parameters) throws Exception {
                        super.open(parameters);
                        reducingState = getRuntimeContext().getReducingState(
                                new ReducingStateDescriptor<Integer>(
                                        "reducingState",
                                        new ReduceFunction<Integer>() {
                                            @Override
                                            public Integer reduce(Integer integer, Integer t1) throws Exception {
                                                return integer + t1;
                                            }
                                        },
                                        Types.INT

                                )
                        );
                    }

                    @Override
                    public void processElement(WaterSensor waterSensor, Context context, Collector<String> collector) throws Exception {
                        // 来一条添加一条
                        reducingState.add(waterSensor.getVc());
                        Integer sum = reducingState.get();
                        collector.collect("传感器id为" + waterSensor.getId() + ",其水位和为" + sum);
                    }
                })
                .print();


        env.execute();
    }

聚合状态(AggregatingState)

        类似于规约状态,但状态以累加器的形式进行存储。

        常用方法如下:

                vcAvgAggregatingState.get();    // 对 本组的聚合状态 获取结果
                vcAvgAggregatingState.add();    // 对 本组的聚合状态 添加数据,会自动进行聚合
                vcAvgAggregatingState.clear();  // 对 本组的聚合状态 清空数据

        示例:计算每个传感器的平均水位值

        这里聚合状态内存储累加器 Tuple2<sum,count>,但是在定义聚合状态的时候,泛型为<IN,OUT>,定义描述器时,泛型为<IN,AGG,OUT>。而在processElement时,add传入IN类型,通过get状态返回值为OUT类型。

public static void main(String[] args) throws Exception {
            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
            env.setParallelism(1);

            SingleOutputStreamOperator<WaterSensor> sensorDS = env
                    .socketTextStream("node1", 7777)
                    .map(new WaterSensorMapFunction())
                    .assignTimestampsAndWatermarks(
                            WatermarkStrategy
                                    .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                    .withTimestampAssigner((value,ts) -> value.getTs()*1000L)
                    );

            sensorDS
                    .keyBy(value -> value.getId())
                    .process(new KeyedProcessFunction<String, WaterSensor, String>() {

                        @Override
                        public void processElement(WaterSensor waterSensor, Context context, Collector<String> collector) throws Exception {

                            // 将 水位值 添加到  聚合状态中
                            aggregatingState.add(waterSensor.getVc());
                            // 从 聚合状态中 获取结果
                            Double result = aggregatingState.get();
                            collector.collect("传感器id为" + waterSensor.getId() + ",平均水位值为" + result);
                        }

                        // AGG : Tuple2<sum,count>
                        // OUT : avg
                        AggregatingState<Integer,Double> aggregatingState;

                        @Override
                        public void open(Configuration parameters) throws Exception {
                            super.open(parameters);
                            aggregatingState = getRuntimeContext().getAggregatingState(
                                    // IN, ACC, OUT
                                    new AggregatingStateDescriptor<Integer, Tuple2<Integer,Integer>,Double>(
                                            "aggregatingState",
                                            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 integer, Tuple2<Integer, Integer> agg) {
                                                    return Tuple2.of(agg.f0+integer,agg.f1+1);
                                                }

                                                @Override
                                                public Double getResult(Tuple2<Integer, Integer> agg) {
                                                    // 进行简单的类型转换,int/int返回都变了会报错
                                                    return agg.f0 * 1D / agg.f1;
                                                }

                                                @Override
                                                public Tuple2<Integer, Integer> merge(Tuple2<Integer, Integer> integerIntegerTuple2, Tuple2<Integer, Integer> acc1) {
                                                    return null;
                                                }
                                            },
                                            Types.TUPLE(Types.INT,Types.INT)
                                    ));



                        }
                             }
                    )
                    .print();

            env.execute();

}

以上五种都是按键分区状态,还有一种状态叫算子状态,这里介绍一下广播状态。

广播状态(BroadcastState)

        广播状态以map形式存在,key可以是配置名称,value为具体的值

        实现案例:水位超过指定的阈值发送告警,阈值可以动态修改

        这里直接通过调用broadcast方法即可,传一个MapStateDescriptor,key为阈值,value为设置的具体值。

       

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(2);

        SingleOutputStreamOperator<WaterSensor> sensorDS = env.socketTextStream("node1", 7777)
                .map(new WaterSensorMapFunction());

        // 配置流(用来传配置)
        DataStreamSource<String> configDS = env.socketTextStream("node1", 8888);

        // TODO 1. 将 配置流 广播
//        new MapStateDescriptor<>()
        MapStateDescriptor<String, Integer> broadcastState = new MapStateDescriptor<>("broadcast-state", Types.STRING, Types.INT);
        BroadcastStream<String> configBS = configDS.broadcast(broadcastState);

        // TODO 2.把 数据流 和 广播后的配置流 connect
        BroadcastConnectedStream<WaterSensor, String> connect = sensorDS.connect(configBS);

        // TODO 3.调用 process
        connect
                .process(
                        new BroadcastProcessFunction<WaterSensor, String, String>() {
                            @Override
                            public void processElement(WaterSensor waterSensor, ReadOnlyContext readOnlyContext, Collector<String> collector) throws Exception {

                                // TODO 5.通过上下文获取广播状态,取出里面的值(只读,不能修改)
                                ReadOnlyBroadcastState<String, Integer> broadcastState2 = readOnlyContext.getBroadcastState(broadcastState);
                                Integer threshold = broadcastState2.get("threshold");
                                threshold = threshold==null?0:threshold;
                                if (waterSensor.vc > threshold){
                                    collector.collect(waterSensor + ",水位超过阈值:" + threshold);
                                }
                            }

                            @Override
                            public void processBroadcastElement(String s, Context context, Collector<String> collector) throws Exception {
                                // TODO 4. 通过上下文获取广播状态,往里面写数据
                                // key统一设置为阈值
                                BroadcastState<String, Integer> broadcastState1 = context.getBroadcastState(broadcastState);
                                broadcastState1.put("threshold",Integer.valueOf(s));
                            }
                        }
                )
                .print();

        env.execute();

    }

        注意两个流传入数据的先后数据,如果配置流后传数据,就没有一个初始的阈值,此时可以将阈值加个null判断设置为0。这里也使用了新的process方法,BroadcastProcessFunction,基于没有keyby过的连接流调用,分别处理数据流和广播流的数据。

        需要注意的是,广播状态在process方法外申明,要使用需要通过上下文对象取得,只有广播流的processElement方法可以修改状态值,对数据流是只读状态。

状态生存时间(TTL)

        在实际生产中,状态需要即使的清除,某些场景不适合调用clear,因此flink支持设置状态生存时间,其设置代码如下。

public void open(Configuration parameters) throws Exception {
                        super.open(parameters);

                        // TODO 2.创建 StateTtlConfig
                        StateTtlConfig ttl = StateTtlConfig
                                // 设置过期时间为5s
                                .newBuilder(Time.seconds(5))
                                // 创建或写操作 更新
                                .updateTtlOnCreateAndWrite()
                                // 不返回过期的状态值
                                .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
                                .build();

                        // TODO 3.状态描述器 启用 TTL
                        ValueStateDescriptor<Integer> valueStateDescriptor = new ValueStateDescriptor<>("valueState", Types.INT);
                        valueStateDescriptor.enableTimeToLive(ttl);

                        // TODO 4.在open方法中,初始化状态
                        // 状态描述器两个参数:第一个参数,起个名字,不重复;第二个参数,存储的类型
                        lastValueState = getRuntimeContext().getState(valueStateDescriptor);

                    }

        例如使用上面的示例,TTL过期时间为5s,只有创建或者执行write操作时更新,意思是:调用process方法的时候,会更新一个TTL,只要接下来在5s内写入数据,生存时间又会更新为5s,状态值仍然存在,但间隔大于5s了,状态值会重新new一个。

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("node1", 7777)
                .map(new WaterSensorMapFunction())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner((value,ts) -> value.getTs()*1000L)
                );

        sensorDS
                .keyBy(value -> value.getId())
                .process(new KeyedProcessFunction<String, WaterSensor, String>() {

                    @Override
                    public void processElement(WaterSensor waterSensor, Context context, Collector<String> collector) throws Exception {
                        // 取出上一条水位数据(存储在值状态中)
                        int lastVc = lastValueState.value() == null ? 0 : lastValueState.value();
                        Integer vc = waterSensor.vc;
                        if(Math.abs(lastVc-vc) > 10){
                            collector.collect("传感器:" + context.getCurrentKey() + "当前水位值" + vc + ",上一条水位值" + lastVc + "相差超过10");
                        }

                        lastValueState.update(vc);
                    }

                    // TODO 1.定义状态
                    ValueState<Integer> lastValueState;

                    @Override
                    public void open(Configuration parameters) throws Exception {
                        super.open(parameters);

                        // TODO 2.创建 StateTtlConfig
                        StateTtlConfig ttl = StateTtlConfig
                                // 设置过期时间为5s
                                .newBuilder(Time.seconds(5))
                                // 创建或写操作 更新
                                .updateTtlOnCreateAndWrite()
                                // 不返回过期的状态值
                                .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
                                .build();

                        // TODO 3.状态描述器 启用 TTL
                        ValueStateDescriptor<Integer> valueStateDescriptor = new ValueStateDescriptor<>("valueState", Types.INT);
                        valueStateDescriptor.enableTimeToLive(ttl);

                        // TODO 4.在open方法中,初始化状态
                        // 状态描述器两个参数:第一个参数,起个名字,不重复;第二个参数,存储的类型
                        lastValueState = getRuntimeContext().getState(valueStateDescriptor);

                    }

                })
                .print();

        env.execute();
    }

        图中事件事件为11和12的数据其实在手动输入时,间隔了超过5秒,因此在12数据执行processElement方法时,他的上一条vc值为null,差值大于10,输出。

状态后端

        分为HashMapStateBackend和RocksDB,前者存储在内存里(TaskManager的JVM上),后者存储在本地硬盘,写入时需要序列化。

        显然,两者的读写性能hash快很多,但是要考虑一点,状态的数据量很大的时候,如果使用哈希表状态后端,TaskManager的内存不一定够用,这时候就使用RocksDS,把状态持久化到硬盘。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值