大数据学习之Flink——12State管理与恢复

本文参考尚学堂Flink课程的课件
侵权删

一. State介绍

Flink 是一个默认就有状态的分析引擎,前面的 WordCount 案例可以做到单词的数量的累加,其实是因为在内存中保证了每个单词的出现的次数,这些数据其实就是状态数据。但是如果一个 Task 在处理过程中挂掉了,那么它在内存中的状态都会丢失,所有的数据都需 要重新计算。从容错和消息处理的语义(At -least-once 和 Exactly-once)上来说,Flink引入了 State 和 CheckPoint。

  1. State 一般指一个具体的 Task/Operator 的状态,State 数据默认保存在 Java 的堆内存中
  2. CheckPoint(可以理解为 CheckPoint 是把 State 数据持久化存储了)则表示了一个 Flink Job 在一个特定时刻的一份全局状态快照,即包含了所有 Task/Operator 的状态。

二. 常用State

Flink 有两种常见的 State 类型:

  1. keyed State(键控状态)
  2. Operator State(算子状态)
1. keyed State(键控状态)

Keyed State:顾名思义就是基于 KeyedStream 上的状态,这个状态是跟特定的Key绑定的。KeyedStream 流上的每一个 Key,都对应一个 State。Flink 针对 Keyed State 提供了以下可以保存 State 的数据结构

  1. ValueState: 保存一个可以更新和检索的值(如上所述,每个值都对应到当前的输 入数据的 key,因此算子接收到的每个 key 都可能对应一个值)。 这个值可以通过update(T) 进行更新,通过 T value() 进行检索。
  2. ListState: 保存一个元素的列表。可以往这个列表中追加数据,并在当前的列表上 进行检索。可以通过 add(T) 或者 addAll(List) 进行添加元素,通过 Iterable get() 获得整个列表。还可以通过 update(List) 覆盖当前的列表。
  3. MapState: 维护了一个映射列表。 你可以添加键值对到状态中,也可以获得 反映当前所有映射的迭代器。使用 put(UK,UV) 或者 putAll(Map) 添加映射。 使用 get(UK) 检索特定 key。使用 entries(),keys() 和 values() 分别检索映射、键和值的可迭代视图。
2. Operator State(算子状态)

Operator State 与 Key 无关,而是与 Operator 绑定,整个 Operator 只对应一个 State。
比如:Flink 中的 Kafka Connector 就使用了 Operator State,它会在每个 Connector 实例 中,保存该实例消费 Topic 的所有(partition, offset)映射。

3. Keyed State 案例
  1. 问题: 计算每个手机的呼叫间隔时间,单位是毫秒

  2. 代码1: 富函数类

    package com.hjf.state
    
    import com.hjf.dataSource.StationLog
    import org.apache.flink.api.common.functions.RichFlatMapFunction
    import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
    import org.apache.flink.configuration.Configuration
    import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
    import org.apache.flink.util.Collector
    
    /**
     * @author Jiang锋时刻
     * @create 2020-07-11 23:38
     */
    object TestKeyedState1 {
      def main(args: Array[String]): Unit = {
        val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
        streamEnv.setParallelism(1)
        import org.apache.flink.streaming.api.scala._
    
        val stream: DataStream[StationLog] = streamEnv.readTextFile(getClass.getResource("/station.log").getPath)
          .map(one => {
            val arr: Array[String] = one.split(",")
            new StationLog(arr(0).trim, arr(1).trim, arr(2).trim, arr(3).trim, arr(4).trim.toLong, arr(5).trim.toLong)
          })
    
        stream.keyBy(_.callOut).flatMap(new CallIntervalFunction()).print()
        streamEnv.execute()
      }
    
      // 输出一个二元组: (手机号, 时间间隔)
      class CallIntervalFunction extends RichFlatMapFunction[StationLog, (String, Long)] {
        // 定义一个保存前一条呼叫状态的数据的状态对象
        private var preData: ValueState[StationLog] = _
    
        override def open(parameters: Configuration): Unit = {
          val stateDescriptor: ValueStateDescriptor[StationLog] = new ValueStateDescriptor[StationLog]("pre", classOf[StationLog])
          preData = getRuntimeContext.getState(stateDescriptor)
        }
    
        override def flatMap(in: StationLog, collector: Collector[(String, Long)]): Unit = {
          // 从状态中获取前一次呼叫的时间
          val pre: StationLog = preData.value()
          // 状态中没有数据, 肯定是第一次访问
          if(pre == null) {
            preData.update(in)
          } else {
          	preData.update(out)
            // 计算时间间隔
            val interval: Long = in.callTime - pre.callTime
            collector.collect((in.callOut, interval))
          }
        }
      }
    }
    
    
  3. 代码2: mapWithState

    package com.hjf.state
    
    import com.hjf.dataSource.StationLog
    import org.apache.flink.api.common.functions.RichFlatMapFunction
    import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
    import org.apache.flink.configuration.Configuration
    import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
    import org.apache.flink.util.Collector
    
    /**
     * @author Jiang锋时刻
     * @create 2020-07-11 23:38
     */
    object TestKeyedState2 {
      def main(args: Array[String]): Unit = {
        val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
        streamEnv.setParallelism(1)
        import org.apache.flink.streaming.api.scala._
    
        val stream: DataStream[StationLog] = streamEnv.readTextFile(getClass.getResource("/station.log").getPath)
          .map(one => {
            val arr: Array[String] = one.split(",")
            new StationLog(arr(0).trim, arr(1).trim, arr(2).trim, arr(3).trim, arr(4).trim.toLong, arr(5).trim.toLong)
          })
    
        stream.keyBy(_.callOut)
          .mapWithState[(String, Long), StationLog]{
          	// 如果状态中没有,则存入
            case(out:StationLog, None) => ((out.callOut, 0), Some(out))
            // 如果状态中有值则计算时间间隔
            case(out:StationLog, pre: Some[StationLog]) => {
              val interval: Long = out.callTime - pre.get.callTime
              ((out.callOut, interval), Some(out))
            }
          }.print()
        streamEnv.execute()
      }
    }
    
  4. 代码3: flatMapWithState

    stream.keyBy(_.callOut)
      	.flatMapWithState[(String, Long), StationLog]{
    		// 如果状态中没有,则存入
            case(out:StationLog, None) => (List.empty, Some(out))
            // 如果状态中有值则计算时间间隔
            case(out:StationLog, pre: Some[StationLog]) => {
              val interval: Long = out.callTime - pre.get.callTime
              (List((out.callOut, interval)), Some(out))
            }
          }.print()
    

三. CheckPoint

当程序出现问题需要恢复 Sate 数据的时候,只有程序提供支持才可以实现 State 的容错。State 的容错需要依靠CheckPoint 机制,这样才可以保证 Exactly-once 这种语义,但是注意,它只能保证Flink系统内的Exactly-once,比如Flink内置支持的算子。针对Source和 Sink 组件,如果想要保证 Exactly-once 的话,则这些组件本身应支持这种语义。

比如本地文件, flink就不支持Exactly-once
比如Kafka, Redis, flink支持Exactly-once

1. CheckPoint原理
  1. Flink 中基于异步轻量级的分布式快照技术提供了 Checkpoints 容错机制,分布式快照 可以将同一时间点 Task/Operator 的状态数据全局统一快照处理,包括前面提到的 Keyed State 和 Operator State。Flink 会在输入的数据集上间隔性地生成 checkpoint barrier,
    通过栅栏(barrier)将间隔时间段内的数据划分到相应的 checkpoint 中。如下图:
    大数据学习之Flink——12State管理与恢复_第1张图片

    当算子接收到barrier时, 会将之前的内容进行持久化保存,
    同时barrier继续向后发送, 最终所有的算子都会进行持久化保存.
    在配置时要尽量避免第一个barrier还没有持久化完之前, 第二个barrier又开始进行持久化了.

  2. 从检查点(CheckPoint)恢复如下图:

大数据学习之Flink——12State管理与恢复_第2张图片

2. CheckPoint 参数和设置

默认情况下 Flink 不开启检查点的,用户需要在程序中通过调用方法配置和开启检查 点,另外还可以调整其他相关参数:

  1. Checkpoint 开启和时间间隔指定:
    开启检查点并且指定检查点时间间隔为 1000ms,根据实际情况自行选择,如果状态比 较大,则建议适当增加该值。

    streamEnv.enableCheckpointing(1000);
    
  2. exactly-ance 和 at-least-once 语义选择:
    选择 exactly-once 语义保证整个应用内端到端的数据一致性,这种情况比较适合于数 据要求比较高,不允许出现丢数据或者数据重复,与此同时,Flink 的性能也相对较弱,而 at-least-once 语义更适合于时廷和吞吐量要求非常高但对数据的一致性要求不高的场景。 如下通过 setCheckpointingMode() 方法来设定语义模式,默认情况下使用的是exactly-once 模式。

    streamEnv.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACT LY_ONCE)
    //或者 
    streamEnv.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.AT_LEAST_ONCE)
    
  3. Checkpoint 超时时间:
    超时时间指定了每次Checkpoint 执行过程中的上限时间范围, 一旦 Checkpoint 执行时间超过该阈值, Flink 将会中断Checkpoint 过程,并按照超时处理。该指标可以通过setCheckpointTimeout 方法设定, 默认为10分钟。

    streamEnv.getCheckpointConfig.setCheckpointTimeout(50000)
    
  4. 检查点之间最小时间间隔:
    该参数主要目的是设定两个 Checkpoint 之间的最小时间间隔,防止出现例如状态数据 过大而导致 Checkpoint 执行时间过长,从而导致 Checkpoint 积压过多,最终 Flink 应用密集地触发 Checkpoint 操作,会占用了大量计算资源而影响到整个应用的性能
    streamEnv.getCheckpointConfig.setMinPauseBetweenCheckpoints(600)

  5. 最大并行执行的检查点数量:
    通过setMaxConcurrentCheckpoints()方法设定能够最大同时执行的Checkpoint数量。 在默认情况下只有一个检查点可以运行,根据用户指定的数量可以同时触发多个Checkpoint,进而提升 Checkpoint 整体的效率。

    streamEnv.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
    
  6. 是否删除 Checkpoint 中保存的数据:
    设置为 RETAIN_ON_CANCELLATION:表示一旦 Flink 处理程序被 cancel 后,会保留 CheckPoint 数据,以便根据实际需要恢复到指定的 CheckPoint。
    设置为 DELETE_ON_CANCELLATION:表示一旦 Flink 处理程序被 cancel 后,会删除 CheckPoint 数据,只有 Job 执行失败的时候才会保存 CheckPoint

    //删除 
    streamEnv.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION)
    //保留
    streamEnv.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)
    
  7. 设置可以容忍的检查的失败数:
    设置可以容忍的检查的失败数,超过这个数量则系统自动关闭和停止任务。

    streamEnv.getCheckpointConfig.setTolerableCheckpointFailureNumber(1)
    

四. 保存机制StateBackend(状态后端)

默认情况下, State会保存在TaskManager的内存中, CheckPoint 会存储在JobManager的内存中. State和CheckPoint的存储位置取决于StateBackend的配置. Flink一共提供了3种StateBackend. 包括:

  1. 基于内存的MemoryStateBackend
  2. 基于文件系统的FsStateBackend
  3. 基于RockDB作为存储介质的RocksDBState-Backend
1. MemoryStateBackend
  • 基于内存的状态管理具有非常快速和高效的特点,但也具有非常多的限制,最主要的就 是内存的容量限制,一旦存储的状态数据过多就会导致系统内存溢出等问题,从而影响整个 应用的正常运行。同时如果机器出现问题,整个主机内存中的状态数据都会丢失,进而无法
    恢复任务中的状态数据。
    大数据学习之Flink——12State管理与恢复_第3张图片

    streamEnv.setStateBackend(new MemoryStateBackend(10*1024*1024))
    
2. FsStateBackend
  • FsStateBackend是基于文件系统的一种状态管理器, 这里的文件系统可以是本地文件系统,也可以是 HDFS 分布式文件系统。FsStateBackend 更 适合任务状态非常大的情况,例如应用中含有时间范围非常长的窗口计算,或 Key/value State 状态数据量非常大的场景。

    大数据学习之Flink——12State管理与恢复_第4张图片

    streamEnv.setStateBackend(new FsStateBackend("hdfs://hadoop101:9000/checkpoint/cp1"))
    
3. RocksDBStateBackend
  • RocksDBStateBackend是Flink中内置的第三方状态管理器,和前面的状态管理器不同, RocksDBStateBackend 需要单独引入相关的依赖包到工程中。

     
    	org.apache.flink</groupId>
    	flink-statebackend-rocksdb_2.11</artifactId>
    	1.9.1</version>
    </dependency>
    

    RocksDBStateBackend 采用异步的方式进行状态数据的 Snapshot,任务中的状态数据首 先被写入本地 RockDB 中,这样在 RockDB 仅会存储正在进行计算的热数据,而需要进行
    CheckPoint 的时候,会把本地的数据直接复制到远端的 FileSystem 中。

    大数据学习之Flink——12State管理与恢复_第5张图片

    streamEnv.setStateBackend( new RocksDBStateBackend ("hdfs://hadoop101:9000/checkpoint/cp2"))
    

    与FsStateBackend相比,RocksDBStateBackend在性能上要比FsStateBackend高一些, 主要是因为借助于 RocksDB 在本地存储了最新热数据,然后通过异步的方式再同步到文件系 统中,但 RocksDBStateBackend 和 MemoryStateBackend 相比性能就会较弱一些。RocksDB克服了 State 受内存限制的缺点,同时又能够持久化到远端文件系统中,推荐在生产中使用。

4. 全局配置 StateBackend
  • 以上的代码都是单 job 配置状态后端,也可以全局配置状态后端,需要修改 flink-conf.yaml 配置文件:

    state.backend: filesystem
    

    大数据学习之Flink——12State管理与恢复_第6张图片

    1.filesystem 表示使用 FsStateBackend,
    2. jobmanager 表示使用 MemoryStateBackend
    3. rocksdb 表示使用 RocksDBStateBackend。

五. CheckPoint案例

1. 代码
  1. 代码

    package com.hjf.state
    
    import org.apache.flink.runtime.state.filesystem.FsStateBackend
    import org.apache.flink.streaming.api.CheckpointingMode
    import org.apache.flink.streaming.api.environment.CheckpointConfig.ExternalizedCheckpointCleanup
    import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
    
    object CheckPointOnFsBackend {
      def main(args: Array[String]): Unit = {
        val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
        streamEnv.setParallelism(1)
        import org.apache.flink.streaming.api.scala._
    
        // 开启CheckPoint, 并且设置一些参数
        streamEnv.enableCheckpointing(5000L)
        // 存设置放CheckPoint数据的路径
        streamEnv.setStateBackend(new FsStateBackend("hdfs://node02:8020/checkpoint/cp1"))
        // 设置CheckPoint模式, EXACTLY_ONCE: 唯一执行一次
        streamEnv.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)
        // 设置CheckPoint超时时间
        streamEnv.getCheckpointConfig.setCheckpointTimeout(50000L)
        // 设置最大并发CheckPoint
        streamEnv.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
        // 终止job时会保留checkpoint数据
        streamEnv.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)
    
        val stream: DataStream[String] = streamEnv.socketTextStream("node01", 8888)
        stream.flatMap(_.split(" ")).map((_, 1)).keyBy(0).sum(1).print()
        streamEnv.execute()
      }
    }
    
    
2. 通过Web UI执行
  1. 输入数据, 查看运行结果
    在这里插入图片描述
    大数据学习之Flink——12State管理与恢复_第7张图片

  2. 查看hdfs中的目录
    大数据学习之Flink——12State管理与恢复_第8张图片
    大数据学习之Flink——12State管理与恢复_第9张图片

  3. 取消任务
    在这里插入图片描述

  4. 重新运行, 查看运行结果
    大数据学习之Flink——12State管理与恢复_第10张图片
    大数据学习之Flink——12State管理与恢复_第11张图片

    大数据学习之Flink——12State管理与恢复_第12张图片

3. 通过命令执行
  1. 启动任务

    ./flink run -c com.hjf.state.CheckPointOnFsBackend /root/Demo-1.0-SNAPSHOT.jar
    

    大数据学习之Flink——12State管理与恢复_第13张图片

  2. 取消任务

    ./flink cancel b23ec0af4ad66c842fcbced28ca89696
    

    在这里插入图片描述

    .查看当前正在运行的任务及其ID: /flink list

    在这里插入图片描述

  3. 重启任务

    ./flink run -c com.hjf.state.CheckPointOnFsBackend /root/Demo-1.0-SNAPSHOT.jar 
    -d -s hdfs://node02:8020/checkpoint/cp1/b23ec0af4ad66c842fcbced28ca89696/chk-14
    

    在这里插入图片描述

六. SavePoint

Savepoints 是检查点的一种特殊实现,底层实现其实也是使用 Checkpoints 的机制。 Savepoints 是用户以手工命令的方式触发 Checkpoint,并将结果持久化到指定的存储路径 中,其主要目的是帮助用户在升级和维护集群过程中保存系统中的状态数据,避免因为停机 运维或者升级应用等正常终止应用的操作而导致系统无法恢复到原有的计算状态的情况,从而无法实现从端到端的 Excatly-Once 语义保证。

1. 配置savepoints的存储路径
  • 在 flink-conf.yaml 中配置 SavePoint 存储的位置,设置后,如果要创建指定 Job 的 SavePoint,可以不用在手动执行命令时指定 SavePoint 的位置。

    state.savepoints.dir: hdfs://node02:8020/savepoint/
    

    大数据学习之Flink——12State管理与恢复_第14张图片

2. 在代码中设置算子 ID

为了能够在作业的不同版本之间以及 Flink 的不同版本之间顺利升级,强烈推荐通过手动给算子赋予ID,这些 ID 将用于确定每一个算子的状态范围。如果不手动给各算子 指定 ID,则会由 Flink 自动给每个算子生成一个 ID。
而这些自动生成的 ID 依赖于程序的结构,并且对代码的更改是很敏感的。因此,强烈建议手动设置 ID。

3. 代码
  • package com.hjf.state
    
    import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
    
    /**
     * @author Jiang锋时刻
     * @create 2020-07-12 11:13
     */
    object TestSavePoints {
      def main(args: Array[String]): Unit = {
        val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
        streamEnv.setParallelism(1)
        import org.apache.flink.streaming.api.scala._
    
        val stream: DataStream[String] = streamEnv.socketTextStream("node01", 8888).uid("stream01")
        stream.flatMap(_.split(" ")).uid("flatMap01")
          .map((_,1)).uid("map01")
          .keyBy(0)
          .sum(1).uid("sum01")
          .print()
        streamEnv.execute()
      }
    }
    
    
4. 运行Web UI运行
  1. 启动任务
    大数据学习之Flink——12State管理与恢复_第15张图片

  2. 手动保存savepoint

    ./flink savepoint e0e9adf803207c8ed361996d2ac50179
    

    在这里插入图片描述

  3. HDFS中查看保存路径
    大数据学习之Flink——12State管理与恢复_第16张图片

  4. 取消任务
    在这里插入图片描述

  5. 重启任务, 并指定savepoint保存的路径
    大数据学习之Flink——12State管理与恢复_第17张图片

  6. 运行结果
    大数据学习之Flink——12State管理与恢复_第18张图片

5. 通过命令行运行
  1. 启动任务

    ./flink run -c com.hjf.state.TestSavePoints -d /root/Flink-1.0-SNAPSHOT.jar
    
  2. 手动执行savepoint

    ./flink savepoint b9e3a7f42f01554bf6df26043950721d
    

    大数据学习之Flink——12State管理与恢复_第19张图片

  3. 取消任务

    ./flink cancel b9e3a7f42f01554bf6df26043950721d
    

    在这里插入图片描述

  4. 重启任务

    ./flink run -s hdfs://node02:8020/savepoint/savepoint-b9e3a7-0c0acbfaa7f1 -c
    com.hjf.state.TestSavePoints -d /root/Flink-1.0-SNAPSHOT.jar
    

    在这里插入图片描述

你可能感兴趣的:(大数据学习,Flink)