Flink从入门到上天系列第十六篇:Flink当中的键控状态

一:值状态

1:需求

连续检测水位值,如果同一个传感器的水位值连续两次超过10就报警

2:不使用状态写法

复制代码
public class Flink01_keyedState_ValueState {
    public static void main(String[] args) throws Exception {
        // 1. 准备环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        // 2. 配置并行度
        env.setParallelism(1);
        // 3. 读取数据
        DataStreamSource<String> socketDs = env.socketTextStream("bigdata137", 8888);
        // 4. 数据类型转换
        SingleOutputStreamOperator<WaterSensor> wsDs = socketDs.map(new WaterSensorMapFunction());
        // 5. 按水位id分组
        KeyedStream<WaterSensor, String> keyedDs = wsDs.keyBy(WaterSensor::getId);
        // 6. 对分组后的数据进行处理
        SingleOutputStreamOperator<String> process = keyedDs.process(
                new KeyedProcessFunction<String, WaterSensor, String>() {
                    Map<String,Integer> lastvMap = new HashMap<>();
                    @Override
                    public void processElement(WaterSensor value, KeyedProcessFunction<String, WaterSensor, String>.Context ctx, Collector<String> out) throws Exception {
                        Integer vc = value.getVc();
                        String currentKey = ctx.getCurrentKey();
                        Integer i = lastvMap.get(value.getId());
                        i = i == null ? 0 : i;
                        if(Math.abs(vc - i) > 10 ){
                            out.collect("传感器id"+currentKey+"是当前水位值"+vc+"和上一次水位值"+i+"之差>10");
                        }
                        lastvMap.put(ctx.getCurrentKey(),i);
                    }
                }
        );
        //7. 打印输出
        process.print();
        //8. 提交作业
        env.execute();
    }
}

当前的问题,使用普通变量,当前的子任务组都共用一个变量,这个作用范围是非常危险的。

3:使用值状态

值状态就是ValueState

复制代码
package com.dashu.day08;

import com.dashu.bean.WaterSensor;
import com.dashu.bean.WaterSensorMapFunction;
import org.apache.flink.api.common.functions.RuntimeContext;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
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.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;

import java.util.HashMap;
import java.util.Map;

public class Flink01_keyedState_ValueState2 {
    public static void main(String[] args) throws Exception {
        // 1. 准备环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        // 2. 配置并行度
        env.setParallelism(1);
        // 3. 读取数据
        DataStreamSource<String> socketDs = env.socketTextStream("bigdata137", 8888);
        // 4. 数据类型转换
        SingleOutputStreamOperator<WaterSensor> wsDs = socketDs.map(new WaterSensorMapFunction());
        // 5. 按水位id分组
        KeyedStream<WaterSensor, String> keyedDs = wsDs.keyBy(WaterSensor::getId);
        // 6. 对分组后的数据进行处理
        SingleOutputStreamOperator<String> process = keyedDs.process(
                new KeyedProcessFunction<String, WaterSensor, String>() {

                    //声明值状态
                    // 注意:虽然声明在成员百年来那个的位置,作用范围不是每一个子任务,而是keyby之后的每一组。这个事flink自己就帮我们做了,
                    // 注意:不能在声明的时候直接对状态进行初始化。(初始化是在对象创建的时候,这个时候算子的生命周期还没开始,拿不到运行时上下文。)
                    ValueState<Integer> lastVcState;


                    @Override
                    // 复函数,有一个open方法。给状态进行初始化。
                    public void open(Configuration parameters) throws Exception {
                        // 对于键控状态来讲,在oepn方法中进行初始刷
                        // 获取运行时上下文
                        RuntimeContext runtimeContext = getRuntimeContext();
                        // 值状态描述器。
                        ValueStateDescriptor<Integer> lastVsState = new ValueStateDescriptor<>("lastVsState", Integer.class);
                        lastVcState= runtimeContext.getState(lastVsState);

                    }

                    @Override
                    public void processElement(WaterSensor value, KeyedProcessFunction<String, WaterSensor, String>.Context ctx, Collector<String> out) throws Exception {
                        //1. 获取当前水位值
                        Integer curVC = value.getVc();
                        //2. 获取上次水位值
                        Integer lastVc = lastVcState.value();
                        lastVc = lastVc == null? 0:lastVc;
                        //3. 获取key
                        String currentKey = ctx.getCurrentKey();

                        // 更新状态
//                        Integer value1 = lastVcState.update();
//                        // 清理掉状态
//                        Integer value1 = lastVcState.clear();
                        if(Math.abs(curVC -lastVc) > 10 ){
                            out.collect("传感器id"+currentKey+"是当前水位值"+lastVc+"和上一次水位值"+lastVc+"之差>10");
                        }

                        //4. 将当前水位值更新到水位中
                        lastVcState.update(curVC);
                    }
                }
        );
        //7. 打印输出
        process.print();
        //8. 提交作业
        env.execute();
    }
}

输入内容:

复制代码
[root@bigdata137 ~]# nc -lk 8888
ws1,1,1
ws1,1,10
ws1,10,1
ws1,100,100
ws2,1,1
ws2,10,1
ws2,11,11
ws2,100,111
ws3,1,1
ws3,1,100

输出内容:

复制代码
传感器idws1是当前水位值1和上一次水位值1之差>10
传感器idws2是当前水位值11和上一次水位值11之差>10
传感器idws3是当前水位值1和上一次水位值1之差>10

4:总结

针对算子内部每一组都维护了状态。彼此之间不相干,不干扰。

二:列表状态

1:需求

复制代码
 输出每种传感器最高的3个水位值。

2:使用列表状态

核心内容提取:ListState<T> 接口与操作方法

  1. 基本定位

ListState<T> 是用于以列表形式组织和管理状态数据 的接口,其中泛型 T 表示列表中存储的数据类型,其操作方式与普通 List 集合高度相似。

  1. 核心方法
  • Iterable<T> get():获取当前列表状态,返回一个可迭代的 Iterable<T> 类型对象。
  • update(List<T> values):传入一个列表 values,直接覆盖当前状态列表。
  • add(T value):向状态列表中添加单个元素 value
  • addAll(List<T> values):向状态列表中批量添加多个元素 ,以列表 values 的形式传入。
  1. 状态描述器

ListState 对应的状态描述器为 ListStateDescriptor,其用法与 ValueStateDescriptor 完全一致。

3:案例示例

复制代码
// 输出每种传感器最高的3个水位值。
public class Flink01_keyedState_ListState {

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


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

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

                            ListState<Integer> vcListState;

                            @Override
                            // 在这个open方法当中对状态进行初始化。
                            public void open(Configuration parameters) throws Exception {
                                //获取运行时上下文+获取ListState+添加ListState描述器+创建状态描述器的时候需要指定状态名称
                                vcListState = getRuntimeContext().getListState(new ListStateDescriptor<Integer>("vcListState", Types.INT));
                            }

                            @Override
                            public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
                                // 1.来一条,存到list状态里
                                vcListState.add(value.getVc());

                                // 2.从list状态拿出来(Iterable), 拷贝到一个List中,排序, 只留3个最大的
                                Iterable<Integer> vcListIt = vcListState.get();
                                // 2.1 拷贝到List中
                                List<Integer> vcList = new ArrayList<>();
                                for (Integer vc : vcListIt) {
                                    vcList.add(vc);
                                }
                                // 2.2 对List进行降序排序
                                vcList.sort((o1, o2) -> o2 - o1);
                                // 2.3 只保留最大的3个(list中的个数一定是连续变大,一超过3就立即清理即可)
                                if (vcList.size() > 3) {
                                    // 将最后一个元素清除(第4个)
                                    vcList.remove(3);
                                }

                                out.collect("传感器id为" + value.getId() + ",最大的3个水位值=" + vcList.toString());

                                // 3.更新list状态
                                vcListState.update(vcList);


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

        env.execute();
    }
}

4:案例分析

从结果上可以清晰的看到,State和State之间是天然隔离的。不会数据共享

三:Map状态

1:需求

复制代码
统计每种传感器的每种水位值出现的次数

2:使用Map状态

复制代码
package com.dashu.day08;

import com.dashu.bean.WaterSensor;
import com.dashu.bean.WaterSensorMapFunction;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
import org.apache.flink.api.common.state.MapState;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

// 统计每种传感器的每种水位值出现的次数。
public class Flink04_keyedState_MapState {

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


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

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

                            MapState<Integer,Integer> vcCountMapState;

                            @Override
                            // 在这个open方法当中对状态进行初始化。
                            public void open(Configuration parameters) throws Exception {
                                //获取运行时上下文+获取ListState+添加ListState描述器+创建状态描述器的时候需要指定状态名称
                                vcCountMapState = getRuntimeContext().getMapState(new MapStateDescriptor<>("vcCountMapState", Types.INT,Types.INT));
                            }

                            @Override
                            public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
// 1.判断是否存在vc对应的key
                                Integer vc = value.getVc();
                                if (vcCountMapState.contains(vc)) {
                                    // 1.1 如果包含这个vc的key,直接对value+1
                                    Integer count = vcCountMapState.get(vc);
                                    vcCountMapState.put(vc, ++count);
                                } else {
                                    // 1.2 如果不包含这个vc的key,初始化put进去
                                    vcCountMapState.put(vc, 1);
                                }

                                // 2.遍历Map状态,输出每个k-v的值
                                StringBuilder outStr = new StringBuilder();
                                outStr.append("======================================\n");
                                outStr.append("传感器id为" + value.getId() + "\n");
                                for (Map.Entry<Integer, Integer> vcCount : vcCountMapState.entries()) {
                                    outStr.append(vcCount.toString() + "\n");
                                }
                                outStr.append("======================================\n");

                                out.collect(outStr.toString());


//                                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状态,清空
                            }
                        }
                )
                .print();

        env.execute();
    }
}

3:案例分析

四:Reducing状态

1:案例

复制代码
统计每种传感器的每种传感器的水位和

2:代码

复制代码
// 统计每种传感器的每种传感器的水位和
public class Flink05_keyedState_ReduceState {

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


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

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


                            @Override
                            public void processElement(WaterSensor value, KeyedProcessFunction<String, WaterSensor, String>.Context ctx, Collector<String> out) throws Exception {
                                Integer vc = value.getVc();
                                // 我们这里调用的是add方法实际会调用open中的传入的计算方法。
                                sumVcState.add(vc);

                                // 从状态中获取累加结果
                                Integer i = sumVcState.get();

                                out.collect("当前传感器"+ctx.getCurrentKey()+"水位和是:"+i);

                            }

                            private ReducingState<Integer> sumVcState;
                            @Override
                            public void open(Configuration parameters) throws Exception {
                                ReducingStateDescriptor<Integer> sumVcState1 = new ReducingStateDescriptor<>("sumVcState", new ReduceFunction<Integer>() {
                                    @Override
                                    public Integer reduce(Integer value1, Integer value2) throws Exception {
                                        return value1+value1;
                                    }
                                }, Integer.class);

                                sumVcState = this.getRuntimeContext().getReducingState(sumVcState1);
                            }
                        }
                )
                .print();

        env.execute();
    }
}

3:案例分析

复制代码
[root@bigdata137 ~]# nc -lk 7777
ws1,1,10
ws1,1,20
ws2,1,10
ws2,1,11
ws1,1,15
ws2,1,5
ws3,1,1

3> 当前传感器ws1水位和是:10
3> 当前传感器ws1水位和是:20
2> 当前传感器ws2水位和是:10
2> 当前传感器ws2水位和是:20
3> 当前传感器ws1水位和是:40
2> 当前传感器ws2水位和是:40
3> 当前传感器ws3水位和是:1

五:聚合状态

1:定义

与归约状态非常类似,聚合状态也是一个值,用来保存添加进来的所有数据的聚合结果。与 ReducingState 不同的是,它的聚合逻辑是由在描述器中传入一个更加一般化的聚合函数(AggregateFunction)来定义的;这也就是之前我们讲过的 AggregateFunction,里面通过一个累加器(Accumulator)来表示状态,所以聚合的状态类型可以跟添加进来的数据类型完全不同,使用更加灵活。

同样地,AggregatingState 接口调用方法也与 ReducingState 相同,调用.add () 方法添加元

2:代码查看

复制代码
package com.dashu.day08;

import com.dashu.bean.WaterSensor;
import com.dashu.bean.WaterSensorMapFunction;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.ReduceFunction;
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.common.typeinfo.Types;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;

import java.time.Duration;

// 计算每种传感器的水位平均水位
public class Flink06_keyedState_AggregatingState {

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


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

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

                            private AggregatingState<Integer,Double> vcAvgState;
                            @Override
                            public void open(Configuration parameters) throws Exception {
                                AggregatingStateDescriptor<Integer, Tuple2<Integer, Integer>, Double> descriptor =
                                        new AggregatingStateDescriptor<Integer, Tuple2<Integer, Integer>, Double>(
                                                "vcAvgState",
                                                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) {
                                                        accumulator.f0 += value;
                                                        accumulator.f1 +=1;
                                                        return accumulator;
                                                    }

                                                    @Override
                                                    public Double getResult(Tuple2<Integer, Integer> accumulator) {
                                                        Integer f0 = accumulator.f0;
                                                        Integer f1 = accumulator.f1;
                                                        return f0*1d/f1;
                                                    }

                                                    @Override
                                                    public Tuple2<Integer, Integer> merge(Tuple2<Integer, Integer> a, Tuple2<Integer, Integer> b) {
                                                        return null;
                                                    }
                                                },
                                                // 累加器的类型
                                                Types.TUPLE(Types.INT,Types.INT)

                                        );

                                vcAvgState = getRuntimeContext().getAggregatingState(descriptor);

                            }
                            @Override
                            public void processElement(WaterSensor value, KeyedProcessFunction<String, WaterSensor, String>.Context ctx, Collector<String> out) throws Exception {
                                Integer vc = value.getVc();
                                //将当前水位值放到状态当中,实际上调用的是上边我们自己写的方法。
                                vcAvgState.add(vc);

                                // 从状态中获取水位值
                                Double v = vcAvgState.get();

                                out.collect("传感器iD="+ctx.getCurrentKey()+"的水位平均值是:"+v);

                            }
                        }
                )
                .print();

        env.execute();
    }
}

3:案例分析

ws1,1,10

ws1,1,20

ws1,1,30

ws1,1,40

ws2,1,10

ws2,1,10

ws2,1,20

复制代码
3> 当前传感器ws1水位和是:10
3> 当前传感器ws1水位和是:20
3> 当前传感器ws1水位和是:40
3> 当前传感器ws1水位和是:80
2> 当前传感器ws2水位和是:10
2> 当前传感器ws2水位和是:20
2> 当前传感器ws2水位和是:40

七:总结

状态:Flink替你开辟内存,替你转状态进行序列化,替你进行容错恢复。

复制代码
状态:
  用途: 用于保存程序运行的中间结果
状态分类:
  原始状态:
    描述: 由程序员自己开辟内存,自己负责状态的序列化以及容错恢复等
  托管状态:
    描述: 由Flink框架管理状态的存储、序列化以及容错恢复等
    子分类:
      算子状态:
        作用范围: 算子的每一个并行子任务上(分区、并行度、slot)
      键控状态:
        作用范围: 经过keyBy之后的每一个组,组和组之间状态是隔离的
        具体类型:
          - ValueState
          - ListState
          - MapState
          - ReducingState
          - AggregatingState
        使用步骤:
          - 在处理函数类成员变量位置声明状态,注意:虽然在成员变量位置声明,但是作用范围是keyBy后的每一个组
          - 在open方法中对状态进行初始化
          - 在具体的处理函数中使用状态
相关推荐
躺柒2 小时前
读2025世界前沿技术发展报告12机器人技术(下)
大数据·人工智能·ai·机器人·服务机器人·智能机器人·智能服务机器人
凸头3 小时前
RedisSearch 和 Elasticsearch 的 HNSW向量索引对比
大数据·elasticsearch·搜索引擎
Jial-(^V^)3 小时前
微调大模型实现新闻分类
大数据·人工智能·分类
V搜xhliang024611 小时前
机器人建模(URDF)与仿真配置
大数据·人工智能·深度学习·机器学习·自然语言处理·机器人
房产中介行业研习社11 小时前
2026年3月哪些房源管理系统功能全
大数据·运维·人工智能
玄微云12 小时前
2026年通用软件难适配,垂直店务系统反而更省心
大数据·云计算·软件需求
Elastic 中国社区官方博客12 小时前
Elastic 为什么捐赠其 OpenTelemetry PHP 发行版
大数据·开发语言·elasticsearch·搜索引擎·信息可视化·全文检索·php
方向研究13 小时前
ABS生产
大数据
TDengine (老段)13 小时前
TDengine 视图功能使用
大数据·数据库·servlet·时序数据库·tdengine·涛思数据