通过WordCount来理解Flink的状态与容错,checkpoint如何恢复?Flink学习入门(四)

Flink的状态与容错也是flink中的重要部分,那么从经典的wordCount案例出发,先来看代码:

import org.apache.flink.streaming.api.scala._

object wordCount1 {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    val dataDS = env.socketTextStream("bigdata101",8888)

    dataDS.flatMap(_.split(" "))
      .filter(!_.isEmpty)
      .map((_,1))
      .keyBy(_._1)
      .sum(1)
      .print()

    env.execute()
  }
}

在这里插入图片描述
现提出问题:

  1. 该word流在输入过程中项目挂掉了,如果重启项目还能继续无缝衔接吗?
  2. 如果需要按照小时、天聚合计算,求当前时刻的最值等聚合指标,仅靠简单的转换算子可以完成吗?

我们先来解决第一个问题:

  1. 如果想要无缝衔接,首先数据不能重复消费,也就是要实现精准一次性,Flink的checkpoint机制可以做到精准一次性
  2. 同时之前的算子的中间状态要能够进行恢复——Flink提供状态后端管理,提供checkpoint容错

那么这设计到2个重要的知识点:

  1. 有状态计算
    有状态计算可以说是Flink重要的特性之一
    有状态计算:在程序计算过程中,在Flink内部存储计算产生的中间结果,并提供给后续Function或算子结果使用。
  2. checkpoint 容错

不再需要借助类似于Redis外部缓存存储中间结果数据,这种方式需要频繁的和外部系统交互,造成了大量的系统性能开销,且不易保证数据在传输和计算过程中的可靠性,当外部存储发送变化,就可能回影响到Flink内部的计算结果。

Flink的状态类型及应用

在Flink中根据数据集是否根据Key进行分区,将状态分为以下2种

  1. Keyed State(是Operator State的特例)
    Keyed State按照Key对数据集进行了分区,每个Keyed State对应一个(算子 + key)的组合
  2. Operator State
    Operator State 只和并行的算子绑定,与key无关
    在这里插入图片描述

在实际过程中,更多使用的还是Keyed State

  1. Value State[T]:与Key对应单个值的状态,例如统计 user id对应的交易次数,每次用户交易都会在 count状态值上进行更新。 ValueState对应的更新方法是updater(T),取值方法是 T value();
  2. Liststate[T]:与Key对应元素列表的状态,状态中存放元素的Lst列表。例如定义 Liststate存储用户经常访问的P地址。在 ListState中添加元素使用add(T)或者 addAll(List[T])两个方法,获取元素使用 Iterable< T> get()方法,更新元素使用update(List[T])方法;
  3. Reducing State[T]:定义与Key相关的数据元素单个聚合值的状态,用于存储经过指定 Reduce Fucntion计算之后的指标,因此,,Reducing State需要指定Reduce Fucntion完成状态数据的聚合。 Reducing State添加元素使用add(T) 方法获取元素使用 T get()方法;
  4. AggregateFunciton:定义与Key相关的数据元素单个聚合值的状态,用于维护数据元素经过指定AggregatingFunction计算之后的指标。 添加元素使用add(IN)方法,获取元素使用 OUT get()方法。
  5. MapState[UK,UV]:定义与Key对应键值对的状态,用于维护具有 key-value结构类型的状态数据, MapState添加元素使用pu(UK,UV)或者putAl( Map[UK,UV)方法,获取元素使用ge(UK)方法。和 HashMap接口相似, MapState也可以通过entries()、 keys、 values()获取对应的keys或 values的集合。

我们选择其中的ValueState,来实现WordCount(举例,同样的方式可以去试着实现一下ListState以及MapState)

object wordCount1 {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    val dataDS = env.socketTextStream("bigdata101", 8888)

    dataDS.flatMap(_.split(" "))
      .filter(!_.isEmpty)
      .map((_, 1))
      .keyBy(_._1)
      .process(
        new KeyedProcessFunction[String, (String, Int), String] {
          //定义状态值
          private var wordCount: ValueState[Int] = _

          //这里的open方法,只会在项目启动的时候调用一次
          override def open(parameters: Configuration): Unit = {
            //通过getRuntimeContext.getState 获取State,作为初始值
            wordCount = getRuntimeContext.getState(
              new ValueStateDescriptor[Int]("wordCount", classOf[Int])
            )
          }

          override def processElement(value: (String, Int),
                                      ctx: KeyedProcessFunction[String, (String, Int), String]#Context,
                                      out: Collector[String]): Unit = {
            //更新wordCount的值
            wordCount.update(wordCount.value() + 1)
            out.collect(ctx.getCurrentKey + "的单词数为" + wordCount.value())
          }
        }
      )
      .print()
    env.execute()
  }
}

同样实现了wordCount,但是此时如果项目宕机,依然不能恢复。
在这里插入图片描述

学习一下Operator State,我们这里通过CheckpointedFunction接口操作Operator State

在每个算子中,都是以List形式存储的,算子与算子之间的状态数据相互独立。

在这里插入图片描述
在一个算子内,OperatorState的数量 = 该算子并行度,所以一般OperatorState用于sink时候比较多,而要使用OperatorState,必须实现以下2个接口(任意一个)

  1. CheckpointedFunction 可对多种类型的状态进行保存、恢复、初始化;当并行度变化时,支持按照Even-split Redistribution重分布策略(默认)和Union redistribution重分布策略;

  2. ListCheckpointed 只能对List类型的状态进行保存、恢复;当并行度变化时,只支持按照Even-split Redistribution重分布策略

重分布策略有以下两种:

  1. Even-split redistribution:每个算子实例中含有部分状态数据的list列表。 当从 checkpoint 中恢复状态数据或者进行重新分配的时候,List 类型的状态数据将被均匀切割成和并行示例数量一致的子链表(每个子链表的元素个数一致),每个算子获得一个子链表,子链表可能为空,也可能包含一个或多个数据。例如,当一个并行度为1 算子,拥有一个 ListState,其中包含数据 e1、e2,当算子的并行改成 2 的时候,发生重新分配,ListState 被切分成两个子链,算子的并行实例1 获得包含 e1 的ListState, 并行实例2 获得包含 e2 的ListState。

  2. Union redistribution:每个算子实例中含有所有状态数据的 List 列表,当发生重新分配或者从 checkpoint 中恢复状态数据的时候,每个算子都将获取到全部的状态数据。

状态管理器

在Flink中提供了StateBacked来存储和管理Checkpoints过程中的状态数据。

1. MemoryStateBacked

基于内存的状态管理器将状态数据全部存储在JVM堆内存中,非常快速和高效,但限制非常多,第一是内存的容量限制,容易出现系统内存溢出,第二,如果机器出现问题,内存中的状态数据都会丢失,无法找回,所以在生产中不建议使用。

2. FsStateBacked

基于文件系统,可以是本地文件系统,也可以是hdfs分布式文件系统,可恢复

//第二个Boolean类型的参数指定是否以同步的方式进行状态数据记录,同步为true,异步为false,默认为false,
//异步方式能够尽可能避免在checkpoint的过程中影响流式计算任务
new FsStateBackend("file:///data/flink/checkpoint",false)
new FsStateBackend("hdfs://nameservice/flink/checkpoint",false)

3. RocksDBStateBacked

Flink中内置的第三方状态管理器,需要单独引包到工程中

<dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-statebackend-rocksdb_2.11</artifactId>
            <version>1.9.0</version>
</dependency>
new RocksDBStateBackend("hdfs://nameservice/flink/checkpoint")

RocksDBStateBacked采用异步的方式进行状态数据的快照,任务中的状态数据首先被写入 RockDB中,然后再异步地将状态数据写入文件系统中,然后再异步地将状态数据写入文件系统中,这样在 RockDB仅会存储正在进行计算的热数据,对于长时间才更新的数据则写入磁盘中进行存储。而对于体量比较小的元数据状态,则直接存储在 JobManager的内存中。

与 FsStateBackend相比, Rocks DBState Backend在性能上要比 FsState Backend高些,主要是因为借助于 RocksDB存储了最新热数据,然后通过异步的方式再同步到文件系统中,但 RocksDBState Backend和 Memory State Backend相比性能就会较弱一些。

RocksDB通过JN的方式进行数据的交互,而JNI构建在byte数据结构之上,因此每次能够传输的最大数据量为2^31字节(2GB),否则将会导致状态数据无法同步,这是 RocksDB采用JN方式的限制

状态的生命周期

我们在上述说到的状态,根据配置有的保存在了内存,有的保存在了磁盘,但不论存储在哪,如果状态一直不清除反而一直大量增加,尤其是当把mapState当作内部Redis来使用时,大量的kv如果不做清除,可能会影响性能。

在 State TtIConfig中除了通过 new Builder法中设定过期时间的参数是必需的之外,其他参数都是可选的或使用默认值。其中 setUpdateType方法中传入的类型有两种

  1. StateTtIConfig Update Type: On Create And write仅在创建和写入时更新TTL
  2. State TtlConfig Update Type. OnRead And write所有读与写操作都更新TTL

需要注意的是,过期的状态数据根据 UpdateType参数进行配置,只有被写入或者读取的时间才会更新TTL,也就是说如果某个状态指标一直不被使用或者更新,则永远不会触发对该状态数据的清理操作,这种情况可能会导致系统中的状态数据越来越大。目前用户可以使用 StateTtlConfig.cleanup FullSnapshot设定当触发 State Snapshot的时候清理状态数据,需要注意这个配置不适合用于 RocksDB做增量 Checkpointing的操作,另外可以通过 setState Visibility方法设定状态的可见性,根据过期数据是否被清理来确定是否返回状态数据。

  1. StateT’tIConfig. StateVisibility. NeverReturnExpired:状态数据过期就不会返回(默认)
  2. StateTtlConfig. StateVisibility. ReturnExpiredIfNotCleanedUp:状态数据即使过期但没有被清理依然返

Flink 中 State 支持设置 TTL,TTL 只是将时间戳与 userValue 封装起来。

· MapState 的 TTL 是基于 UK 级别的
· ValueState 的 TTL 是基于整个 key 的

什么意思呢?
MapState 的过期是针对MapState中内部的KV结构中的某一个Key
ValueState的过期 是针对当前流所属的Key

Checkpoints和Savepoints

先把代码写了,下面说一下checkpoint机制,以及涉及到的State生命周期

Flink容错机制的核心部分是对分布式数据流和算子状态的一致快照。如果发生故障,系统可以回退到这些检查点。

  1. 每个需要checkpoint的应用在启动时,Flink的JobManager为其创建一个 CheckpointCoordinator,CheckpointCoordinator全权负责本应用的快照制作。
    在这里插入图片描述
  2. CheckpointCoordinator周期性的向该流应用的所有source算子发送barrier。
  3. 当某个source算子收到一个barrier时,便暂停数据处理过程,然后将自己的当前状态制作成快照,并保存到指定的持久化存储中,最后向CheckpointCoordinator报告 自己快照制作情况,同时向自身所有下游算子广播该barrier,恢复数据处理
  4. 下游算子收到barrier之后,会暂停自己的数据处理过程,然后将自身的相关状态制作成快照,并保存到指定的持久化存储中,最后向CheckpointCoordinator报告自身快照情况,同时向自身所有下游算子广播该barrier,恢复数据处理。
  5. 每个算子按照步骤3不断制作快照并向下游广播,直到最后barrier传递到sink算子,快照制作完成。
  6. 当CheckpointCoordinator收到所有算子的报告之后,认为该周期的快照制作成功;否则,如果在规定的时间内没有收到所有算子的报告,则认为本周期快照制作失败 ;

以上是基本的Checkpoint机制,同时接下来我们结合它的一些配置来详细解释一些Checkpoint机制

  1. checkpoint 开启和时间间隔
//单位为毫秒
env.enableCheckpointing(10000)

时间间隔,假定每隔10s,从源头处开始触发一次checkpoint请求

  1. exactly-ance 和at-least-once语义选择
//单位为毫秒,以下2种方式都可以设置
env.enableCheckpointing(10000,CheckpointingMode.EXACTLY_ONCE)
env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)

可以选择 exactly-once语义保证整个应用内端到端的数据一致性,这种情况比较适合于数据要求比较高,不允许出现丢数据或者数据重复,与此同时, Flink的性能也相对较弱,而 at-least-once语义更适合于时延和吞吐量要求非常高但对数据的一致性要求不高的场景。默认情况下使用的是 exactly-once模式。

那么这2者之间,对于Checkpointing来说有什么区别呢?

在多输入流场景中,假设:
我们有2个kafka分区,分别读取,就有2个数据流,分为A和B,A中的单词都是5个字母,B中的单词都是4个字母。

在这里插入图片描述

理论上结果如下:

checkpoint中目前保存的结果是:source端A流的offset消费到了hello,而B流并没有保存offset,所以B流中已经消费了的word会进行重新消费,这也就是flink中的至少一次模式。

而精准一次指的是:
在这里插入图片描述
这也就意味着,在下游已经做了的快照的数据,在上游一定有快照,所以在中断任务重启后并不会重新消费,所以称为精准一次性。

  1. Checkpoint超时时间
    超时时间指定了每次 Checkpoint执行过程中的上限时间范围,一且 Checkpoint执行时间超过该阈值, Flink将会中断 Checkpoint过程,并按照超时处理。默认为10分钟。
env.getCheckpointConfig.setCheckpointTimeout(60000)
  1. 检查点之间最小时间间隔
    该参数主要目的是设定两个 Checkpoint之间的最小时间间隔,防止出现例如状态据过大而导致 Checkpoint执行时间过长,从而导致 Checkpoint积压过多,最终 Flink用密集地触发 Checkpoint操作,会占用了大量计算资源而影响到整个应用的性能。
env. getCheckpointConfig(). setMinPauseBetweencheckpoints(500)
  1. 最大并行执行的检查点数量
    在默认情况下只有一个检查点可以运行,根据用户指定的数量可以同时触发多个checkpoint,进而提升 Checkpoint整体的效率。
  2. 外部检查点
    设定周期性的外部检查点,然后将状态数据持久化到外部系统中,使用这种方式不会在任务正常停止的过程中清理掉检查点数据,而是会一直保存在外部系统介质中,另外也可以通过从外部检查点中对任务进行恢复
env.getCheckpointConfig..enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)
  1. failOnCheckpointingErrors
    该参数决定了当 Checkpoint执行过程中如果出现失败或者错误时,任务是否同时被关闭,默认值为True
env.getcheckpointconfig(). setFailoncheckpointingErrors (false)

Savepoints机制

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

所以区别主要就是:
checkpoint面向Flink Runtime本身,由Flink的各个TaskManager定时触发快照并自动清理,一般不需要用户干预;
savepoint面向用户,完全根据用户的需要触发与清理。

最后

通过以上的学习检查点机制以及状态后端的存储,以下代码终于可以实现wordCount的无缝衔接,下面是代码和测试结果展示:

import org.apache.flink.api.common.state.{StateTtlConfig, ValueState, ValueStateDescriptor}
import org.apache.flink.api.common.time.Time
import org.apache.flink.configuration.Configuration
import org.apache.flink.contrib.streaming.state.RocksDBStateBackend
import org.apache.flink.runtime.state.StateBackend
import org.apache.flink.streaming.api.CheckpointingMode
import org.apache.flink.streaming.api.environment.CheckpointConfig.ExternalizedCheckpointCleanup
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector

object wordCount1 {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    val dataDS = env.socketTextStream("produce101", 6666)
    /*new FsStateBackend("file:///data/flink/checkpoint")
    new FsStateBackend("hdfs://nameservice/flink/checkpoint")*/
    env.enableCheckpointing(10000,CheckpointingMode.EXACTLY_ONCE)
    val dBStateBackend: StateBackend = new RocksDBStateBackend("hdfs://djcluster/Test")
    env.setStateBackend(dBStateBackend)

    env.getCheckpointConfig.setCheckpointTimeout(60000)

    env.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)

    dataDS.flatMap(_.split(" "))
      .filter(!_.isEmpty)
      .map((_, 1))
      .keyBy(_._1)
      .process(
        new KeyedProcessFunction[String, (String, Int), String] {
          //定义状态值
          private var wordCount: ValueState[Int] = _

          //这里的open方法,只会在项目启动的时候调用一次
          override def open(parameters: Configuration): Unit = {
            
            val stateTtlConfig = StateTtlConfig
              //指定TTL时长为3分钟,也就是说3分钟后过期
              .newBuilder(Time.minutes(3))
              //指定TTL刷新时只对创建和写入操作有效
              .setUpdateType(StateTtlConfig.UpdateType.OnReadAndWrite)
              .build()
            val valueDescriptor: ValueStateDescriptor[Int] = new ValueStateDescriptor[Int]("wordCount", classOf[Int])

            //指定创建好的stateTtlConfig
            valueDescriptor.enableTimeToLive(stateTtlConfig)
            //通过getRuntimeContext.getState 获取State,作为初始值
            wordCount = getRuntimeContext.getState(valueDescriptor)
          }

          override def processElement(value: (String, Int),
                                      ctx: KeyedProcessFunction[String, (String, Int), String]#Context,
                                      out: Collector[String]): Unit = {
            //更新wordCount的值
            wordCount.update(wordCount.value() + 1)
            out.collect(ctx.getCurrentKey + "的单词数为" + wordCount.value())
          }
        }
      )
      .print()

    env.execute()
  }
}

为了测试一下状态的生命周期有没有生效,我们先输入hello,等待1分钟后,我们再次输入world,再等待2分钟后,理论上hello这个key绑定的状态值已经过期,而world 这个key绑定的值并没有过期。

在这里插入图片描述
命令:

./bin/flink run -m yarn-cluster -s hdfs://djcluster/Test/311243c1df3d507bb865024250105c47/chk-184/_metadata -yqu spark -c com.later.flink_demo.wordCount1 /usr/local/jars/flink-wordCount.jar  -yn 7 -ys 2 -p 14 -ytm 2048m -yjm 2048m 

特别注意,这里的-s 必须放在前面,参数顺序乱了就不能生效了
接下来我们重启,看能不能继续衔接之前的数据
在这里插入图片描述
结果是非常nice

同时,经过测试,这里的状态生命周期设置的时间3分钟指的是固定时间,如果3分钟内没有当前key的操作,就会过期,不论此时的flink任务是否在执行。

好了,关于一些简单常用的状态管理就学习到这里,欢迎大家指错交流!

扫码关注公众号“后来X大数据”,回复【电子书】,领取超多本pdf 【java及大数据 电子书】

在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值