Flink内部Exactly Once三板斧:状态、状态后端与检查点

Flink是一个分布式的流处理引擎,而流处理的其中一个特点就是7X24。那么,如何保障Flink作业的持续运行呢?Flink的内部会将应用状态(state)存储到本地内存或者嵌入式的kv数据库(RocksDB)中,由于采用的是分布式架构,Flink需要对本地生成的状态进行持久化存储,以避免因应用或者节点机器故障等原因导致数据的丢失,Flink是通过checkpoint(检查点)的方式将状态写入到远程的持久化存储,从而就可以实现不同语义的结果保障。通过本文,你可以了解到什么是Flink的状态,Flink的状态是怎么存储的,Flink可选择的状态后端(statebackend)有哪些,什么是全局一致性检查点,Flink内部如何通过检查点实现Exactly Once的结果保障。另外,本文内容较长,建议关注加收藏。

什么是状态

引子

关于什么是状态,我们先不做过多的分析。首先看一个代码案例,其中案例1是Spark的WordCount代码,案例2是Flink的WorkCount代码。

  • 案例1:Spark WC
object WordCount {
   
  def main(args:Array[String]){
   
  val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
  val ssc = new StreamingContext(conf, Seconds(5))
  val lines = ssc.socketTextStream("localhost", 9999)
  val words = lines.flatMap(_.split(" "))
  val pairs = words.map(word => (word, 1))
  val wordCounts = pairs.reduceByKey(_ + _)
  wordCounts.print()
  ssc.start()
  ssc.awaitTermination()
}
}

输入:

C:\WINDOWS\system32>nc -lp 9999
hello spark
hello spark

输出:
在这里插入图片描述

  • 案例2:Flink WC
public class WordCount {
   
    public static void main(String[] args) throws Exception {
   
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment().setParallelism(1);
        DataStreamSource<String> streamSource = env.socketTextStream("localhost", 9999);
        SingleOutputStreamOperator<Tuple2<String,Integer>> words = streamSource.flatMap(new FlatMapFunction<String, Tuple2<String,Integer>>() {
   
            @Override
            public void flatMap(String value, Collector<Tuple2<String,Integer>> out) throws Exception {
   
                String[] splits = value.split("\\s");
                for (String word : splits) {
   
                    out.collect(Tuple2.of(word, 1));
                }
            }
        });
        words.keyBy(0).sum(1).print();
        env.execute("WC");
    }
}

输入:

C:\WINDOWS\system32>nc -lp 9999
hello Flink
hello Flink

输出:
在这里插入图片描述
从上面的两个例子可以看出,在使用Spark进行词频统计时,当前的统计结果不受历史统计结果的影响,只计算接收的当前数据的结果,这个就可以理解为无状态的计算。再来看一下Flink的例子,可以看出当第二次词频统计时,把第一次的结果值也统计在了一起,即Flink把上一次的计算结果保存在了状态里,第二次计算的时候会先拿到上一次的结果状态,然后结合新到来的数据再进行计算,这就可以理解成有状态的计算,如下图所示。
在这里插入图片描述

状态的类别

Flink提供了两种基本类型的状态:分别是 Keyed StateOperator State。根据不同的状态管理方式,每种状态又有两种存在形式,分别为:managed(托管状态)raw(原生状态)。具体如下表格所示。需要注意的是,由于Flink推荐使用managed state,所以下文主要讨论managed state,对于raw state,本文不会做过多的讨论。
在这里插入图片描述

managed state & raw state区别

在这里插入图片描述

Keyed State & Operator State

在这里插入图片描述

Keyed State

Keyed State只能由作用在KeyedStream上面的函数使用,该状态与某个key进行绑定,即每一个key对应一个state。Keyed State按照key进行维护和访问的,Flink会为每一个Key都维护一个状态实例,该状态实例总是位于处理该key记录的算子任务上,因此同一个key的记录可以访问到一样的状态。如下图所示,可以通过在一条流上使用keyBy()方法来生成一个KeyedStream。Flink提供了很多种keyed state,具体如下:
在这里插入图片描述

  • ValueState

用于保存类型为T的单个值。用户可以通过ValueState.value()来获取该状态值,通过ValueState.update()来更新该状态。使用ValueStateDescriptor来获取状态句柄。

  • ListState

用于保存类型为T的元素列表,即key的状态值是一个列表。用户可以使用ListState.add()或者ListState.addAll()将新元素添加到列表中,通过ListState.get()访问状态元素,该方法会返回一个可遍历所有元素的Iterable对象,注意ListState不支持删除单个元素,但是用户可以使用update(List values)来更新整个列表。使用 ListStateDescriptor来获取状态句柄。

  • ReducingState

调用add()方法添加值时,会立即返回一个使用ReduceFunction聚合后的值,用户可以使用ReducingState.get()来获取该状态值。使用 ReducingStateDescriptor来获取状态句柄。

  • AggregatingState<IN, OUT>

与ReducingState类似,不同的是它使用的是AggregateFunction来聚合内部的值,AggregatingState.get()方法会计算最终的结果并将其返回。使用 AggregatingStateDescriptor来获取状态句柄

  • MapState<UK, UV>

用于保存一组key、value的映射,类似于java的Map集合。用户可以通过get(UK key)方法获取key对应的状态,可以通过put(UK k,UV value)方法添加一个键值,可以通过remove(UK key)删除给定key的值,可以通过contains(UK key)判断是否存在对应的key。使用 MapStateDescriptor来获取状态句柄。

  • FoldingState<T, ACC>

在Flink 1.4的版本中标记过时,在未来的版本中会被移除,使用AggregatingState进行代替。

值得注意的是,上面的状态原语都支持通过State.clear()方法来进行清除状态。另外,上述的状态原语仅用于与状态进行交互,真正的状态是存储在状态后端(后面会介绍状态后端)的,通过该状态原语相当于持有了状态的句柄(handle)。

keyed State使用案例

下面给出一个MapState的使用案例,关于ValueState的使用情况可以参考官网,具体如下:

public class MapStateExample {
   

    //统计每个用户每种行为的个数
    public static class UserBehaviorCnt extends RichFlatMapFunction<Tuple3<Long, String, String>, Tuple3<Long, String, Integer>> {
   

        //定义一个MapState句柄
        private transient MapState<String, Integer> behaviorCntState;

        // 初始化状态
        @Override
        public void open(Configuration parameters) throws Exception {
   
            super.open(parameters);
            MapStateDescriptor<String, Integer> userBehaviorMapStateDesc = new MapStateDescriptor<>(
                    "userBehavior",  // 状态描述符的名称
                    TypeInform
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值