Flink 状态

Flink 状态

相关博客:

Flink_Flink中的状态

Flink状态管理详解:Keyed State和Operator List State深度解析

一、状态概述

在这里插入图片描述

  • 由一个任务维护,并且用来计算某个结果的所有数据,都属于这个任务的状态

  • 可以认为任务状态就是一个本地变量,可以被任务的业务逻辑访问

  • Flink 会进行状态管理,包括状态一致性、故障处理以及高效存储和访问,以便于开发人员可以专注于应用程序的逻辑

  • 在Flink中,状态始终与特定算子相关联

  • 为了使运行时的Flink了解算子的状态,算子需要预先注册其状态

有两种类型的状态:

  • 算子状态(Operator State)

    算子状态的作用范围为算子任务(也就不能跨任务访问)

  • 键控状态(Keyed State)

    根据输入数据流中定义的键来维护和访问的

二、算子状态Operator State

在这里插入图片描述

  • 算子状态的作用范围限定为算子任务,同一并行任务所处理的所有数据都可以访问到相同的状态。
  • 状态对于同一任务而言是共享的。(不能跨slot
  • 状态算子不能由相同或不同算子的另一个任务访问。

算子状态数据结构

  • 列表状态(List state)

    将状态表示为一组数据的列表

  • 联合列表状态(Union list state)

    也将状态表示未数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复

  • 广播状态(Broadcast state)

    如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态

测试代码

实际使用中一般使用键控状态,算子状态用的比较少。

需要实现ListCheckpointed这个接口

package com.root.state;

import com.root.SensorReading;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.streaming.api.checkpoint.ListCheckpointed;
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.Collections;
import java.util.List;

/**
 * @author Kewei
 * @Date 2022/3/6 20:23
 */

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

        DataStreamSource<String> inputStream = env.socketTextStream("localhost", 7777);

        SingleOutputStreamOperator<SensorReading> dataStream = inputStream.map(line -> {
            String[] fields = line.split(",");
            return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
        });

        SingleOutputStreamOperator<Integer> resultStream = dataStream.map(new MyCountMapper());

        resultStream.print();

        env.execute();
    }

    public static class MyCountMapper implements MapFunction<SensorReading, Integer>, ListCheckpointed<Integer>{
        private Integer count = 0;

        @Override
        public Integer map(SensorReading sensorReading) throws Exception {
            count++;
            return count;
        }

        @Override
        public List<Integer> snapshotState(long l, long l1) throws Exception {
            return Collections.singletonList(count);
        }

        @Override
        public void restoreState(List<Integer> list) throws Exception {
            for (Integer integer : list) {
                count += integer;
            }
        }
    }
}

三、键控状态 Keyed State

在这里插入图片描述

  • 键控状态是根据输入数据中定义的键(key)来进行维护和访问的。
  • Flink为每一个key维护一个状态实例,并将具有相同键的所有数据,都分区到同一个算子任务中,这个任务会维护和处理这个key对应的状态。
  • 当任务处理一条数据时,会自动将状态的访问范围限定为当前数据的key

键控状态的数据结构

  • 值状态(value state)
    • 将状态表示为单个的值
  • 列表状态(List state)
    • 将状态表示为一组数据的列表
  • 映射状态(Map state)
    • 将状态表示为一组key-value对
  • 聚合状态(Reducing state & Aggregating State)
    • 将状态表示为一个用于聚合操作的列表,(对象)

测试代码

在这里插入图片描述

注:声明一个键控状态,一般在算子的open()中声明,因为运行时才能获取上下文信息。

需要继承富函数RichFunction

package com.root.state;

import akka.stream.impl.ReducerState;
import com.root.SensorReading;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.state.*;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

/**
 * @author Kewei
 * @Date 2022/3/6 20:38
 */

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

        DataStreamSource<String> inputStream = env.socketTextStream("localhost", 7777);

        SingleOutputStreamOperator<SensorReading> dataStream = inputStream.map(line -> {
            String[] fields = line.split(",");
            return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
        });

        SingleOutputStreamOperator<Integer> resultStream = dataStream
                .keyBy("id")  // 键控需要先分组
                .map(new MyMapper());

        resultStream.print();

        env.execute();

    }

    public static class MyMapper extends RichMapFunction<SensorReading, Integer>{

        // Exception in thread "main" java.lang.IllegalStateException: The runtime context has not been initialized.
        // ValueState<Integer> valueState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("my-int", Integer.class));
        
        // 声明 值状态
        private ValueState<Integer> valueState;
        
        // 声明 列表状态
        private ListState<String> listState;
        
        // 声明 map(key-value)状态
        private MapState<String,Double> mapState;
        
        // 声明聚合状态
        private ReducingState<SensorReading> readingReducingState;

        @Override
        public void open(Configuration par) throws Exception {
            // 在open方法中初始化状态
            valueState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("value",Integer.class));
            listState = getRuntimeContext().getListState(new ListStateDescriptor<String>("list",String.class));

            mapState = getRuntimeContext().getMapState(new MapStateDescriptor<String, Double>("map",String.class,Double.class));
//            readingReducingState = getRuntimeContext().getReducingState(new ReducingStateDescriptor<SensorReading>("reduce",reduce函数,SensorReading.class))
        }

        @Override
        public Integer map(SensorReading value) throws Exception {

            // list state 输出,以及添加值
            for (String s : listState.get()) {
                System.out.println(s);
            }
            listState.add("hello");

            // map state 输出,添加,删除
            mapState.get("1");
            mapState.put("2",2.1);
            mapState.remove("2");

            //reducing state 添加值
//            readingReducingState.add(value);

            // value state 获取值,更新值
            Integer count = valueState.value();

            count = count==null?0:count;
            ++count;
            valueState.update(count);
            return count;
        }
    }
}

假设做一个温度警报,如果一个传感器前后温度差超过10摄氏度就报警。这里使用键控状态keyed State + FlatMap来实现。

package com.root.state;

import com.root.SensorReading;
import org.apache.flink.api.common.functions.RichFlatMapFunction;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.configuration.Configuration;
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 org.apache.flink.util.Collector;

/**
 * @author Kewei
 * @Date 2022/3/6 21:02
 */

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

        DataStreamSource<String> inputStream = env.socketTextStream("localhost", 7777);
        SingleOutputStreamOperator<SensorReading> dataStream = inputStream.map(line -> {
            String[] fields = line.split(",");
            return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
        });

        SingleOutputStreamOperator<Tuple3<String, Double, Double>> resultStream = dataStream
                .keyBy(SensorReading::getId)
                .flatMap(new MyFlatMapper(10.0));

        resultStream.print();
        env.execute();
    }

    public static class MyFlatMapper extends RichFlatMapFunction<SensorReading, Tuple3<String,Double,Double>>{
        // 定义一个温度差预警值
        private final Double th;

        public MyFlatMapper(Double th){
            this.th = th;
        }
        
        // 定义一个值状态,保存上一次的温度
        ValueState<Double> lastTemp;

        @Override
        public void open(Configuration parameters) throws Exception {
            // 获取上一次保存的状态--上一次的温度
            lastTemp = getRuntimeContext().getState(new ValueStateDescriptor<Double>("last",Double.class));
        }

        @Override
        public void close() throws Exception {
            // 释放资源
            lastTemp.clear();
        }

        @Override
        public void flatMap(SensorReading value, Collector<Tuple3<String, Double, Double>> out) throws Exception {
            Double lastTemper = lastTemp.value();
            Double corTemp = value.getTemperature();

            // 判断lastTemper是否为空,之后再判断差值是否大于阈值
            if (lastTemper!=null) {
                if (Math.abs(corTemp - lastTemper) > th) {
                    out.collect(new Tuple3<>(value.getId(),corTemp,lastTemper));
                }
            }

            // 更新保存上一次温度
            lastTemp.update(corTemp);
        }
    }
}

四、状态后端

4.1 概述

  • 每传入一条数据,有状态的算子任务都会读取和更新状态。
  • 由于有效的状态访问对于处理数据的低延迟至关重要,因此每个并行任务都会在本地维护其状态,以确保快速的状态访问。
  • 状态的存储、访问以及维护,由一个可插入的组件决定,这个组件就叫做状态后端( state backend)
  • 状态后端主要负责两件事:本地状态管理,以及将检查点(checkPoint)状态写入远程存储

4.2 状态后端类型

  • MemoryStateBackend
    • 内存级的状态后端,会将键控状态作为内存中的对象进行管理,将它们存储在TaskManager的JVM堆上,而将checkpoint存储在JobManager的内存中
    • 特点:快速、低延迟,但不稳定
  • FsStateBackend(默认)
    • 将checkpoint存到远程的持久化文件系统(FileSystem)上,而对于本地状态,跟MemoryStateBackend一样,也会存在TaskManager的JVM堆上
    • 同时拥有内存级的本地访问速度,和更好的容错保证
  • RocksDBStateBackend
    • 将所有状态序列化后,存入本地的RocksDB中存储
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

努力生活的黄先生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值