Flink状态管理

flink中的状态

flink 广播算子 flink 算子状态_数据

  • 由一个任务维护,并且用来计算某个结果的所有数据,都属于这个任务的状态。
  • 可以认为状态就是一个本地变量,可以被任务的业务逻辑访问
  • Flink 会进行状态管理,包括状态一致性,故障处理以及高效存储和访问,以便开发人员可以专注于应用程序的逻辑。

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

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

总的来说有两种类型的状态:

  • 算子状态(Operatior State)
  • 算子状态的作用范围限定为算子任务
  • 键控状态(Keyed State)
  • 根据输入数据流中定义的键(key)来维护和访问

算子状态(Operatior State)

flink 广播算子 flink 算子状态_flink 广播算子_02

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

算子状态数据结构

  • 列表状态(List state)
  • 将状态表示为一组数据的列表
  • 联合列表状态(Union list state)
  • 也就状态表示为数据的列表。与常规列表的状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复
  • 广播状态(Broadcast state)
  • 如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。
  • 案例代码:
public static void main(String[] args) throws Exception {
      StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
     
      env.setParallelism(1);
    

      //DataStream<String> filedata = env.readTextFile("data/temps.txt");
      DataStream<String> localhost = env.socketTextStream("localhost", 7777);

      DataStream<TempInfo> dataStream = localhost.map(new MapFunction<String, TempInfo>() {
          @Override
          public TempInfo map(String value) throws Exception {
              String[] split = value.split(",");
              return new TempInfo(split[0],new Long(split[1]),new Double(split[2]));
          }
      });

      // 定义一个有状态的map操作,统计当前分区数据个数
      dataStream.map(new MyCounterMap());


      env.execute();
  }

  // 自定义map function
  static class MyCounterMap implements MapFunction<TempInfo,Integer>, ListCheckpointed<Integer>{
      // 定义状态变量
      private Integer count;

      @Override
      public Integer map(TempInfo value) throws Exception {
          count++;
          return count;
      }

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

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

键控状态(Keyed State)

flink 广播算子 flink 算子状态_ide_03

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

键控状态数据结构

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

键控状态的使用

  • 声明一个键控状态
myValueState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("my-value",Integer.class));
  • 读取状态
Integer myValue = myValueState.value();
  • 对状态赋值
myValueState.update(10);
  • 使用测试
public static void main(String[] args) throws Exception {
      StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

      env.setParallelism(1);


      //DataStream<String> filedata = env.readTextFile("data/temps.txt");
      DataStream<String> localhost = env.socketTextStream("localhost", 7777);

      DataStream<TempInfo> dataStream = localhost.map(new MapFunction<String, TempInfo>() {
          @Override
          public TempInfo map(String value) throws Exception {
              String[] split = value.split(",");
              return new TempInfo(split[0],new Long(split[1]),new Double(split[2]));
          }
      });

      dataStream.keyBy(data -> data.getId())
              .map(new MyKeyCounterMapper());

      env.execute();
  }

  static class MyKeyCounterMapper extends RichMapFunction<TempInfo,Integer>{

      private ValueState<Integer> keyCountState;
      private ListState<String> myListState;
      private MapState<String,Double> myMapState;
      private ReducingState<TempInfo> myReduceState;

      @Override
      public void open(Configuration parameters) throws Exception {
          keyCountState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("key-count",Integer.class));
          myListState = getRuntimeContext().getListState(new ListStateDescriptor<String>("my-list",String.class));
          myMapState = getRuntimeContext().getMapState(new MapStateDescriptor<String, Double>("my-map",String.class,Double.class));
          myReduceState = getRuntimeContext().getReducingState(new ReducingStateDescriptor<TempInfo>("my-reduce", new ReduceFunction<TempInfo>() {
              @Override
              public TempInfo reduce(TempInfo value1, TempInfo value2) throws Exception {
                  return new TempInfo();
              }
          }, TempInfo.class));
      }

      @Override
      public Integer map(TempInfo value) throws Exception {
          Integer count = keyCountState.value();
          count++;
          keyCountState.update(count);

          // list操作
          Iterable<String> strings = myListState.get();
          // map操作
          myMapState.put("test",1.2);
          myMapState.get("1");
          myMapState.remove("2");
          // reduce 操作 传入直接进行reduce聚合
          myReduceState.add(value);
          return count;
      }
  }
  • 案例(这里我们要注意在不同并行度下状态收集的触发机制,感兴趣的可以试试关闭并行度为1时代码的运行测试效果)
public static void main(String[] args) throws Exception {
      StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

      env.setParallelism(1);



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

      DataStream<TempInfo> dataStream = localhost.map(new MapFunction<String, TempInfo>() {
          @Override
          public TempInfo map(String value) throws Exception {
              String[] split = value.split(",");
              return new TempInfo(split[0],new Long(split[1]),new Double(split[2]));
          }
      });

      // 定义flatmap操作,监控温度跳变,进行报警
      dataStream.keyBy(data -> data.getId())
              .flatMap(new TempChangeWarn(10.0));

      env.execute();
  }

  static class TempChangeWarn extends RichFlatMapFunction<TempInfo, Tuple3<String,Double,Double>>{
      //定义属性,温度跳变阀值
      private Double threshold;

      public TempChangeWarn(Double threshold){
          this.threshold = threshold;
      }

      // 定义状态,保存上一次状态值
      private ValueState<Double> lastTempState;


      @Override
      public void open(Configuration parameters) throws Exception {
          // 初始化
          lastTempState = getRuntimeContext().getState(new ValueStateDescriptor<Double>("last-temp",Double.class));
      }

      @Override
      public void flatMap(TempInfo value, Collector<Tuple3<String, Double, Double>> out) throws Exception {
          Double lastTemp = lastTempState.value();
          if(lastTemp != null){
              Double diff = Math.abs(value.getTemp() - lastTemp);
              if(diff >= threshold){
                  out.collect(new Tuple3<String, Double, Double>(value.getId(),lastTemp,value.getTemp()));
              }
          }
          // 更新状态
          lastTempState.update(value.getTemp());
      }

      @Override
      public void close() throws Exception {
          lastTempState.clear();
      }
  }

状态后端(State Backends)

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

选择一个状态后端

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

flink配置文件中设置:conf下flink-conf.yaml配置文件

flink 广播算子 flink 算子状态_数据_04

代码中设置:

RocksDBStateBackend

将所有状态序列化后,存入本地的RocksDB中存储。

注意:RocksDB的支持并不直接包含在flink中,需要引入依赖

<dependency>
			<groupId>org.apache.flink</groupId>
			<artifactId>flink-statebackend-rocksdb_${scala.binary.version}</artifactId>
			<version>${flink.version}</version>
	</dependency>
  • 代码设置
env.setStateBackend(new MemoryStateBackend());
env.setStateBackend(new FsStateBackend("hdfs route"));
env.setStateBackend(new RocksDBStateBackend("hdfs route"));