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方法中对状态进行初始化
          - 在具体的处理函数中使用状态
相关推荐
蒸汽求职15 小时前
机器人软件工程(Robotics SDE):特斯拉Optimus落地引发的嵌入式C++与感知算法人才抢夺战
大数据·c++·算法·职场和发展·机器人·求职招聘·ai-native
诸葛务农15 小时前
AGI 主要技术路径及核心技术:归一融合及未来之路5
大数据·人工智能
J2虾虾17 小时前
数据分析师课程
大数据
大力财经18 小时前
纳米漫剧流水线接入满血版Seedance 2.0 实现工业级AI漫剧确定性交付
大数据·人工智能
AI周红伟18 小时前
OpenClaw是什么?OpenClaw能做什么?OpenClaw详细介绍及保姆级部署教程-周红伟
大数据·运维·服务器·人工智能·微信·openclaw
Elastic 中国社区官方博客18 小时前
当 TSDS 遇到 ILM:设计不会拒绝延迟数据的时间序列数据流
大数据·运维·数据库·elasticsearch·搜索引擎·logstash
Omics Pro18 小时前
虚拟细胞:开启HIV/AIDS治疗新纪元的关键?
大数据·数据库·人工智能·深度学习·算法·机器学习·计算机视觉
沐风___19 小时前
Claude Code 权限模式完全指南:Auto、Bypass、Ask 三模式深度解析
大数据·elasticsearch·搜索引擎
qq_54702617920 小时前
LangChain 工具调用(Tool Calling)
java·大数据·langchain