Flink状态和键控流

6.8 Flink状态编程

有状态的计算是流处理框架要实现的重要功能,因为稍复杂的流处理场景都需要记录状态,然后在新流入数据的基础上不断更新状态。

6.8.1 Flink中的状态分类

Flink包括两种基本类型的状态Managed State和Raw State

Managed StateRaw State
状态管理方式Flink Runtime托管, 自动存储, 自动恢复, 自动伸缩用户自己管理
状态数据结构Flink提供多种常用数据结构, 例如:ListState, MapState等字节数组: byte[]
使用场景绝大数Flink算子所有算子

注意:

  1. 从具体使用场景来说,绝大多数的算子都可以通过集成Rich函数类或其他提供好的接口类,在里面使用Managed State。Raw State一般是在已有算子和Managed State不够用时,自定义算子时使用。

  2. 在我们平时的使用中,Managed State已经足够使用。

6.8.2 Managed State的分类

对Managed State继续细分,它又有两种类型:Keyed State(键控状态)、Operator State(算子状态)

Operator StateKeyed State
适用用算子类型可用于所有算子: 常用于source, 例如 FlinkKafkaConsumer只适用于KeyedStream上的算子
状态分配一个算子的子任务对应一个状态一个Key对应一个State: 一个算子会处理多个Key, 则访问相应的多个State
创建和访问方式实现CheckpointedFunction或ListCheckpointed(已经过时)接口重写RichFunction, 通过里面的RuntimeContext访问
横向扩展并发改变时有多重重写分配方式可选: 均匀分配合并后每个得到全量并发改变, State随着Key在实例间迁移
支持的数据结构ListState和BroadCastStateValueState, ListState,MapState ReduceState, AggregatingState
6.8.3 算子状态(Operator State)的使用
(1)算子状态特点

Operator State可以用在所有算子上,每个算子子任务或者说每个算子实例共享一个状态,流入这个算子子任务的数据可以访问和更新这个状态。

注意:算子子任务之间的状态不能互相访问。

Operator State的实际使用场景不如Keyed State多,它经常被用在Source和Sink等算子上,用来保留流入数据的偏移量或对输出数据做缓存,以保证Flink应用的Exactly-Once语义。

image-20210127114513263

(2)算子状态三种基本数据结构:

发生故障时(并行度改变了),或者从保存点savepoint启动应用程序时数据如何恢复?

  • 列表状态(List State)

    将状态表示为一组数据的列表,当并行度发生变化,数据均匀分配给多个运行实例。

    image-20210127120318209

  • 联合列表状态(Union List State)

    将所有state合并为全量state,再分发给每个运行实例。

    image-20210127120542201

  • 广播状态(Broadcast State)

1. 一个并行实例 一个状态
2. 自定义算子状态,要实现一个CheckpointedFunction接口,
    实现两个方法snapshotState()和initializeState()
3. 算子状态的数据结构是一个listState:根据出错数据恢复的方式分为:
	1) 普通的listState
	2) 联合列表状态Union list State
public class Flink26_State_Operator {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(3);

        env
                .socketTextStream("hadoop102", 9999)
                .map(new MyCountMapper())
                .print();
        env.execute();
    }

    public static class MyCountMapper implements MapFunction<String, Long>, CheckpointedFunction{
        private Long count = 0L;
        private ListState<Long> state;

        @Override
        public Long map(String value) throws Exception {
            count++;
            return count;
        }

        /*
         * @Description //TODO checkpoint时会调用这个方法,要实现具体的snapshot逻辑,比如将哪些本地状态初始化
         * @Param [context]
         * @return void
         **/
        @Override
        public void snapshotState(FunctionSnapshotContext context) throws Exception {
            System.out.println("snapshotState...");
            state.clear();
            state.add(count);
        }

        /*
         * @Description //TODO 初始化,向本地状态中填充数据,每个子任务调用一次
         * @Param [context]
         * @return void
         **/
        @Override
        public void initializeState(FunctionInitializationContext context) throws Exception {
            System.out.println("initializeState...");
            state = context.getOperatorStateStore()
                    .getListState(new ListStateDescriptor<Long>("state", Types.LONG));
            for (Long c : state.get()) {
                count += c;
            }
        }
    }
}
6.8.4 键控状态(Keyed State)的使用
(1)键控状态特点

键控状态是根据输入数据流中定义的key来维护和访问的,也就是说一个key 一个state(一个分组一个状态)

(2)键控状态支持的数据类型
  • ValueState

    保存单个值,每个key(分组)有一个状态

    • 步骤1:定义 ValueState valueState;

    • 步骤2:初始化,open()内 valueState = getRuntimeContext().getState(new ValueStateDescriptor(状态名,状态类型,初始值))

    • 步骤3:操作状态

      • 获取状态存储的值:valueState.value();
      • 更新状态的值:update()
      • 清空本组的状态值:clear()
  • ListState

    保存list结构的多个值

  • MapState<UK, UV>

    保存map结构

  • ReducingState

    保存单个值,把所有元素的结果聚合,添加到状态中,输入和输出的类型必须一样

  • AggregatingState<IN, OUT>
    保存单个值,把元素进行聚合。不同于reducingState的是AggregatingState的输入和输出类型可以不一样。

(3)键控流状态API
public class Flink27_State_Keyed {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("hadoop102", 9999)
                .map(new MapFunction<String, WaterSensor>() {
                    @Override
                    public WaterSensor map(String value) throws Exception {
                        String[] split = value.split(",");
                        return new WaterSensor(split[0], Long.valueOf(split[1]), Integer.valueOf(split[2]));
                    }
                })
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner(new SerializableTimestampAssigner<WaterSensor>() {   // 指定如何从数据中提取事件时间
                                    @Override
                                    public long extractTimestamp(WaterSensor element, long recordTimestamp) {
                                        return element.getTs() * 1000L; // 转换成 毫秒
                                    }
                                })
                );

        sensorDS
                .keyBy(sensor -> sensor.getId())
                //    键控状态: keyby之后
                //          一个分组对应一个状态,分组之间是隔离的,跟并行度无关
                .process(new KeyedProcessFunction<String, WaterSensor, String>() {
                    //TODO 1 定义状态
                    ValueState<Integer> valueState;
                    ListState<WaterSensor> listState;
                    MapState<String, Long> mapState;

                    //TODO 2 在open方法里,初始化状态
                    @Override
                    public void open(Configuration parameters) throws Exception {
                        //TODO 3.1 ValueState
                        //从richFunction的运行时环境中获取状态,需要给状态起名字,指明类型,也可以设置初始值
                        valueState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("valueState", Types.INT));
                        //Integer状态的初始值是null,第三个参数是默认值。
//                        valueState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("valueState", Integer.class, 0));

                        //TODO 3.2 ListState
                        listState = getRuntimeContext().getListState(new ListStateDescriptor<WaterSensor>("listState", WaterSensor.class));

                        //TODO 3.3 MapState
                        mapState = getRuntimeContext().getMapState(new MapStateDescriptor<String, Long>("mapState", Types.STRING, Types.LONG));


                    }

                    @Override
                    public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
//                        //TODO 4.1 ValueState
//                        valueState.value();     //获取状态存储的值
//                        valueState.update(1);   //更新状态值
//                        valueState.clear();     //清空状态,注意,清空的是本组的状态,value是哪个组的,就清空哪个组
//
//                        //TODO 4.2 ListState
//                        Iterable<WaterSensor> waterSensors = listState.get();   //获取状态里的数据,返回一个可迭代类型
//                        listState.add(new WaterSensor("sensor_1", 1L, 1));  //添加单个值
//                        listState.addAll(); //添加整个list
//                        listState.update(); //更新整个list
//                        listState.clear();  //清空本组的状态
//
//                        //TODO 4.3 MapState
//                        mapState.get(); //通过某个key,获取对应的value
//                        mapState.put(); //添加一对key - value
//                        mapState.putAll();  //添加整个map
//                        mapState.contains();    //判断是否包含某个key
//                        mapState.remove();  //删除某个key对应的数据
//                        mapState.clear();   //清空本组的状态
                    }
                })
                .print("result");

        env.execute();
    }
}
(4)键控流状态案例1:获取上一条数据的水位值
public class Flink28_State_Keyed {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("hadoop102", 9999)
                .map(new MapFunction<String, WaterSensor>() {
                    @Override
                    public WaterSensor map(String value) throws Exception {
                        String[] split = value.split(",");
                        return new WaterSensor(split[0], Long.valueOf(split[1]), Integer.valueOf(split[2]));
                    }
                })
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner(new SerializableTimestampAssigner<WaterSensor>() {   // 指定如何从数据中提取事件时间
                                    @Override
                                    public long extractTimestamp(WaterSensor element, long recordTimestamp) {
                                        return element.getTs() * 1000L; // 转换成 毫秒
                                    }
                                })
                );

        sensorDS
                .keyBy(sensor -> sensor.getId())
                .process(new KeyedProcessFunction<String, WaterSensor, String>() {
                    //TODO 1 初始化状态
                    ValueState<Integer> lastVCState;
                    //如果不用状态保存,而是用变量保存,那么不会分组
                    int lastVC = 0;

                    @Override
                    public void open(Configuration parameters) throws Exception {
                        //TODO 2 初始化状态
                        lastVCState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("lastVCState", Types.INT, 0));
                    }

                    @Override
                    public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
                        //TODO 3 处理数据
//                        Integer value1 = lastVCState.value();
//                        out.collect("当前key=" + ctx.getCurrentKey() + ", 上一次的水位值=" + value1);
//                        lastVCState.update(value.getVc());

                        //如果不使用状态,使用变量保存:
                        out.collect("当前key" + ctx.getCurrentKey() + ", 上一次的水位值=" + lastVC);
                        lastVC = value.getVc();

                    }
                })
                .print();

        env.execute();
    }
}
(5)键控流状态案例2:改造定时器
public class Flink29_State_KeyedState {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        env
                .socketTextStream("hadoop102", 9999)
                .map(new MapFunction<String, WaterSensor>() {
                    @Override
                    public WaterSensor map(String value) throws Exception {
                        String[] dats = value.split(",");
                        return new WaterSensor(
                                dats[0],
                                Long.valueOf(dats[1]),
                                Integer.valueOf(dats[2])
                        );
                    }
                })
                .assignTimestampsAndWatermarks(WatermarkStrategy
                        .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                        .withTimestampAssigner(new SerializableTimestampAssigner<WaterSensor>() {
                            @Override
                            public long extractTimestamp(WaterSensor element, long recordTimestamp) {
                                return element.getTs() * 1000L;
                            }
                        })
                )
                .keyBy(sensor -> sensor.getId())
                .process(new KeyedProcessFunction<String, WaterSensor, String>() {
                    //TODO 1 定义状态
                    ValueState<Long> timeTs;
                    ValueState<Integer> lastVC;

                    //TODO 2 初始化状态
                    @Override
                    public void open(Configuration parameters) throws Exception {
                        timeTs =getRuntimeContext().getState(new ValueStateDescriptor<Long>("timeTs", Types.LONG));
                        lastVC = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("lastVC", Types.INT, 0));
                    }

                    //TODO 3 处理数据
                    @Override
                    public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
                        if (timeTs.value() == null){
                            //TODO 3.1 第一条进来,注册一个5s后的定时器
                            ctx.timerService().registerEventTimeTimer(ctx.timestamp() + 5000L);
                            timeTs.update(ctx.timestamp() + 5000L);
                        }
                        if (value.getVc() < lastVC.value()){
                            //于上一条数据比较水位,判断上升还是下降
                            //下降
                            //1. 删除原来的定时器
                            ctx.timerService().deleteEventTimeTimer(timeTs.value());
                            //2. 注册新的定时器
                            ctx.timerService().registerEventTimeTimer(ctx.timestamp() + 5000L);
                            timeTs.update(ctx.timestamp() + 5000L);
                        }
                        //保存水位值,给下一次数据做判断
                        lastVC.update(value.getVc());
                    }

                    //TODO 4 定时器触发
                    @Override
                    public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
                        timeTs.clear();
                        out.collect("传感器=" + ctx.getCurrentKey() + "在ts=" + timestamp + "监测到水位连续5s上升");
                    }
                })
                .print("result");

        env.execute();
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

最佳第六六六人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值