Flink笔记04——一文了解State管理和恢复

前言

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

常用State

Flink 有两种常见的 State 类型,分别是:

  • keyed State(键控状态)
  • Operator State(算子状态)

Keyed State(键控状态)

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

  • ValueState: 保存一个可以更新和检索的值(如上所述,每个值都对应到当前的输 入数据的 key,因此算子接收到的每个 key 都可能对应一个值)。 这个值可以通过 update(T) 进行更新,通过 T value() 进行检索。
  • ListState: 保存一个元素的列表。可以往这个列表中追加数据,并在当前的列表上 进行检索。可以通过 add(T) 或者 addAll(List) 进行添加元素,通过 Iterable get() 获得整个列表。还可以通过 update(List) 覆盖当前的列表。
  • ReducingState: 保存一个单值,表示添加到状态的所有值的聚合。接口与 ListState 类似,但使用 add(T) 增加元素,会使用提供的 ReduceFunction 进行聚合。
  • AggregatingState<IN, OUT>: 保留一个单值,表示添加到状态的所有值的聚合。和 ReducingState 相反的是, 聚合类型可能与 添加到状态的元素的类型不同。 接口与 ListState 类似,但使用 add(IN) 添加的元素会用指定的 AggregateFunction 进行聚 合。
  • FoldingState<T, ACC>: 保留一个单值,表示添加到状态的所有值的聚合。 与 ReducingState 相反,聚合类型可能与添加到状态的元素类型不同。接口与 ListState 类似,但使用 add(T)添加的元素会用指定的 FoldFunction 折叠成聚合值。
  • MapState<UK, UV>: 维护了一个映射列表。 你可以添加键值对到状态中,也可以获得 反映当前所有映射的迭代器。使用 put(UK,UV) 或者 putAll(Map<UK,UV>) 添加映射。 使用 get(UK) 检索特定 key。使用 entries(),keys() 和 values() 分别检索映射、 键和值的可迭代视图。

demo: 计算每个手机的呼叫时间间隔时间

package com.flink.primary.State

import com.flink.primary.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 xjh 2020.4.7
  */
  
//定义一个外部类
case class StationLog(sid: String, callOut: String, callIn: String, callType: String, callTime: Long, duration: Long)

object TestKeyedState1 {
  def main(args: Array[String]): Unit = {
    val environment = StreamExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.streaming.api.scala._
    //读取数据源
    val filePath=getClass.getResource("/station.log").getPath
    val stream = environment.readTextFile(filePath)
      .map(line => {
        val strings = line.split(",")
        new StationLog(strings(0).trim, strings(1).trim, strings(2).trim, strings(3).trim, strings(4).trim.toLong, strings(5).trim.toLong)
      })

    stream.keyBy(_.callOut) //分组
      .flatMap(new CallIntervalFunction) //flatmap中传一个富函数类
      .print()

    environment.execute()
  }

  //自定义一个类,继承富函数类(富函数类可在上下文环境中管理状态)
  class CallIntervalFunction extends RichFlatMapFunction[StationLog, (String, Long)] { //输出的一个二元组(String,Long) 手机号码,时间间隔
    private var preCallTimeState: ValueState[Long] = _

    override def open(parameters: Configuration): Unit = {
      preCallTimeState = getRuntimeContext.getState(new ValueStateDescriptor[Long]("pre", classOf[Long]))
    }

    override def flatMap(value: StationLog, out: Collector[(String, Long)]): Unit = {
      //从状态中取得前一次的时间
      var preCallTime = preCallTimeState.value()
      if (preCallTime == null || preCallTime == 0) {
        //状态中没有,当前为第一次呼叫
        preCallTimeState.update(value.callTime)
      } else {
        //状态中有数据,则要计算时间间隔
        var interval = Math.abs(value.callTime - preCallTime)
        out.collect((value.callOut,interval)) //返回一个二元组
      }
    }
  }
}

Operator State(算子状态)

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

CheckPoint

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

1.CheckPoint 原理
Flink 中基于异步轻量级的分布式快照技术提供了 Checkpoints 容错机制,分布式快照可以将同一时间点Task/Operator 的状态数据全局统一快照处理,包括前面提到的 Keyed State 和 Operator State。Flink 会在输入的数据集上间隔性地生成 checkpoint barrier,通过栅栏(barrier)将间隔时间段内的数据划分到相应的 checkpoint 中。如下图:
在这里插入图片描述
2.CheckPoint参数和设置
默认情况下 Flink 不开启检查点的,用户需要在程序中通过调用方法配置和开启检查 点,另外还可以调整其他相关参数:

  • Checkpoint 开启和时间间隔指定: 开启检查点并且指定检查点时间间隔为 1000ms,根据实际情况自行选择,如果状态比较大,则建议适当增加该值。
    streamEnv.enableCheckpointing(1000);
  • 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_LE AST_ONCE) 
    
  • Checkpoint 超时时间: 超时时间指定了每次 Checkpoint 执行过程中的上限时间范围,一旦 Checkpoint 执行时间超过该阈值,Flink 将会中断Checkpoint 过程,并按照超时处理。该指标可以通过 setCheckpointTimeout 方法设定,默认为 10 分钟。 streamEnv.getCheckpointConfig.setCheckpointTimeout(50000)
  • 检查点之间最小时间间隔: 该参数主要目的是设定两个 Checkpoint 之间的最小时间间隔,防止出现例如状态数据 过大而导致 Checkpoint 执行时间过长,从而导致 Checkpoint 积压过多,最终 Flink 应用密 集地触发 Checkpoint 操作,会占用了大量计算资源而影响到整个应用的性能。streamEnv.getCheckpointConfig.setMinPauseBetweenCheckpoints(600)
  • 最大并行执行的检查点数量: 通过 setMaxConcurrentCheckpoints()方法设定能够最大同时执行的 Checkpoint 数量。 在默认情况下只有一个检查点可以运行,根据用户指定的数量可以同时触发多个 Checkpoint,进而提升 Checkpoint 整体的效率。streamEnv.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
  • 是否删除 Checkpoint 中保存的数据: 设置为 RETAIN_ON_CANCELLATION:表示一旦 Flink 处理程序被 cancel 后,会保留 CheckPoint 数据,以便根据实际需要恢复到指定的 CheckPoint。 设置为 DELETE_ON_CANCELLATION:表示一旦 Flink 处理程序被 cancel 后,会删除 CheckPoint 数据,只有 Job 执行失败的时候才会保存 CheckPoint.
    //删除 
    streamEnv.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckp ointCleanup.DELETE_ON_CANCELLATION) 
    //保留
    streamEnv.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckp ointCleanup.RETAIN_ON_CANCELLATION)
    
  • TolerableCheckpointFailureNumber: 设置可以容忍的检查的失败数,超过这个数量则系统自动关闭和停止任务。 streamEnv.getCheckpointConfig.setTolerableCheckpointFailureNumber(1)
    注意这些参数设置,不用花时间去记,熟能生巧。

(2020.6.17更新)

Checkpoint 执行机制详解

这一小节转载自Apache Flink 进阶教程(三):Checkpoint 的应用实践 作者 | 唐云(茶干)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

保存机制 StateBackend(状态后端)

默认情况下,State 会保存在 TaskManager 的内存中,CheckPoint 会存储在 JobManager 的内存中。State 和 CheckPoint 的存储位置取决于 StateBackend 的配置。Flink 一共提供 了 3 种 StateBackend 。 包 括 基 于 内 存 的 MemoryStateBackend 、 基 于 文 件 系 统 的 FsStateBackend,以及基于 RockDB 作为存储介质的 RocksDBState-Backend

  1. MemoryStateBackend
    基于内存的状态管理具有非常快速和高效的特点,但也具有非常多的限制,最主要的是内存的容量限制,一旦存储的状态数据过多就会导致系统内存溢出等问题,从而影响整个应用的正常运行。同时如果机器出现问题,整个主机内存中的状态数据都会丢失,进而无法恢复任务中的状态数据。因此从数据安全的角度建议用户尽可能地避免在生产环境中使用 MemoryStateBackend。 streamEnv.setStateBackend(new MemoryStateBackend(10*1024*1024))

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

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

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

    RocksDBStateBackend 采用异步的方式进行状态数据的 Snapshot,任务中的状态数据首 先被写入本地 RockDB 中,这样在 RockDB 仅会存储正在进行计算的热数据,而需要进行 CheckPoint 的时候,会把本地的数据直接复制到远端的 FileSystem 中。
    与 FsStateBackend 相比,RocksDBStateBackend 在性能上要比 FsStateBackend 高一些,主要是因为借助于 RocksDB 在本地存储了最新热数据,然后通过异步的方式再同步到文件系 统中,但 RocksDBStateBackend 和 MemoryStateBackend 相比性能就会较弱一些。RocksDB 克服了 State 受内存限制的缺点,同时又能够持久化到远端文件系统中,推荐在生产中使用。

    streamEnv.setStateBackend( new RocksDBStateBackend ("hdfs://hadoop101:9000/checkpoint/cp2"))
    
  4. 全局配置 StateBackend
    上述都是单 job 配置状态后端,我们也可以全局配置状态后端,这需要修改 flink-conf.yaml 配置文件: state.backend: filesystem
    其中: filesystem 表示使用 FsStateBackend;jobmanager 表示使用 MemoryStateBackend; rocksdb 表示使用 RocksDBStateBackend。

    例如我们设置:state.checkpoints.dir: hdfs://hadoop101:9000/checkpoints
    默认情况下,如果设置了 CheckPoint 选项,则 Flink 只保留最近成功生成的 1 个 CheckPoint,而当 Flink 程序失败时,可以通过最近的 CheckPoint 来进行恢复。但是,如 果希望保留多个 CheckPoint,并能够根据实际需要选择其中一个进行恢复,就会更加灵活。 添加如下配置,指定最多可以保存的 CheckPoint 的个数。 state.checkpoints.num-retained: 2

SavePoint

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

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

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

    object TestSavepoints { 
    	def main(args: Array[String]): Unit = { 
    	//初始化Flink的Streaming(流计算)上下文执行环境 
    	val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment 
    	import org.apache.flink.streaming.api.scala._ 
    	//读取数据得到DataStream 
    		val stream: DataStream[String] = streamEnv.socketTextStream("hadoop101",8888) .uid("mySource-001") 
    		stream.flatMap(_.split(" ")) 
    		.uid("flatMap-001") 
    		.map((_,1)) 
    		.uid("map-001")
    		 .keyBy(0) 
    		 .sum(1) 
    		 .uid("sum-001") 
    		 .print() 
    	streamEnv.execute("wc") //启动流计算 
    	} 
    }
    
  3. 触发 SavePoint

    //先启动Job 
    [root@hadoop101 bin]# ./flink run -c com.bjsxt.flink.state.TestSavepoints -d /home/Flink-Demo-1.0-SNAPSHOT.jar 
    //再取消Job ,触发SavePoint 
    [root@hadoop101 bin]# ./flink savepoint 6ecb8cfda5a5200016ca6b01260b94ce 
    [root@hadoop101 bin]# ./flink cancel 6ecb8cfda5a5200016ca6b01260b94ce
    

在这里插入图片描述在这里插入图片描述
4) 从 SavePoint 启动 Job

[root@hadoop101 bin]# ./flink run -s hdfs://hadoop101:9000/savepoints/savepoint-6ecb8c-e56ccb88576a -c com.bjsxt.flink.state.TestSavepoints -d /home/Flink-Demo-1.0-SNAPSHOT.jar

也可以通过 Web UI 启动 Job:
在这里插入图片描述

最后,一张图简述CheckPoint和Savepoint的区别:
在这里插入图片描述
参考资料:
1.尚学堂大数据技术Flink教案

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章