Spark和Flink的状态管理State的区别和应用
大数据技术与架构
点击右侧关注,大数据开发领域最强公众号!
暴走大数据
点击右侧关注,暴走大数据!
By 大数据技术与架构
场景描述:如果一个task在处理过程中挂掉了,那么它在内存中的状态都会丢失,所有的数据都需要重新计算。那么我就需要一个东西保存历史状态State。
关键词:State Flink Spark
首先区分一下两个概念,state一般指一个具体的task/operator的状态。而checkpoint则表示了一个Job,在一个特定时刻的一份全局状态快照,即包含了所有task/operator的状态。我们在这里讨论的是state。
Spark的状态更新
updateStateByKey
updateStateByKey会统计全局的key的状态,不管又没有数据输入,它会在每一个批次间隔返回之前的key的状态。updateStateByKey会对已存在的key进行state的状态更新,同时还会对每个新出现的key执行相同的更新函数操作。如果通过更新函数对state更新后返回来为none,此时刻key对应的state状态会被删除(state可以是任意类型的数据的结构)。
mapWithState
mapWithState也会统计全局的key的状态,但是如果没有数据输入,便不会返回之前的key的状态,类似于增量的感觉。
updateStateByKey和mapWithState的区别
updateStateByKey可以在指定的批次间隔内返回之前的全部历史数据,包括新增的,改变的和没有改变的。由于updateStateByKey在使用的时候一定要做checkpoint,当数据量过大的时候,checkpoint会占据庞大的数据量,会影响性能,效率不高。
mapWithState只返回变化后的key的值,这样做的好处是,我们可以只是关心那些已经发生的变化的key,对于没有数据输入,则不会返回那些没有变化的key的数据。这样的话,即使数据量很大,checkpoint也不会像updateStateByKey那样,占用太多的存储,效率比较高(再生产环境中建议使用这个)。
updateStateByKey示例:
def updateFunction(currValues:Seq[Int],preValue:Option[Int]): Option[Int] = { val currValueSum = currValues.sum //上面的Int类型都可以用对象类型替换 Some(currValueSum + preValue.getOrElse(0)) //当前值的和加上历史值 } kafkaStream.map(r => (r._2,1)).updateStateByKey(updateFunction _)
这里的updateFunction方法就是需要我们自己去实现的状态跟新的逻辑,currValues就是当前批次的所有值,preValue是历史维护的状态,updateStateByKey返回的是包含历史所有状态信息的DStream。
mapWithState示例:
val initialRDD = ssc.sparkContext.parallelize(List[(String, Int)]()) //自定义mappingFunction,累加单词出现的次数并更新状态 val mappingFunc = (word: String, count: Option[Int], state: State[Int]) => { val sum = count.getOrElse(0) + state.getOption.getOrElse(0) val output = (word, sum) state.update(sum) output } //调用mapWithState进行管理流数据的状态 kafkaStream.map(r => (r._2,1)).mapWithState(StateSpec.function(mappingFunc).initialState(initialRDD)).print()
这里的initialRDD就是初始化状态,updateStateByKey也有对应的API。这里的mappingFun也是需要我们自己实现的状态跟新逻辑,调用state.update()就是对状态的跟新,output就是通过mapWithState后返回的DStream中的数据形式。注意这里不是直接传入的mappingFunc函数,而是一个StateSpec 的对象,其实也是对函数的一个包装而已。
Flink的状态更新
Flink中包含两种基础的状态:Keyed State和Operator State。
Keyed State
顾名思义,就是基于KeyedStream上的状态。这个状态是跟特定的key绑定的,对KeyedStream流上的每一个key,可能都对应一个state。
Operator State
与Keyed State不同,Operator State跟一个特定operator的一个并发实例绑定,整个operator只对应一个state。相比较而言,在一个operator上,可能会有很多个key,从而对应多个keyed state。
举例来说,Flink中的Kafka Connector,就使用了operator state。它会在每个connector实例中,保存该实例中消费topic的所有(partition, offset)映射。
state的整个继承关系
Keyed State
首先看一下Keyed State下,我们可以用哪些原子状态:
- ValueState:即类型为T的单值状态。这个状态与对应的key绑定,是最简单的状态了。它可以通过update方法更新状态值,通过value()方法获取状态值。
- ListState:即key上的状态值为一个列表。可以通过add方法往列表中附加值;也可以通过get()方法返回一个Iterable<T>来遍历状态值。
- ReducingState:这种状态通过用户传入的reduceFunction,每次调用add方法添加值的时候,会调用reduceFunction,最后合并到一个单一的状态值。
- FoldingState:跟ReducingState有点类似,不过它的状态值类型可以与add方法中传入的元素类型不同(这种状态将会在Flink未来版本中被删除)。
- MapState:即状态值为一个map。用户通过put或putAll方法添加元素。
一个创建和使用ValueState的例子:
public class CountWindowAverage extends RichFlatMapFunction<Tuple2<Long, Long>, Tuple2<Long, Long>> { /** * ValueState状态句柄. 第一个值为count,第二个值为sum。 */ private transient ValueState<Tuple2<Long, Long>> sum; @Override public void flatMap(Tuple2<Long, Long> input, Collector<Tuple2<Long, Long>> out) throws Exception { // 获取当前状态值 Tuple2<Long, Long> currentSum = sum.value(); // 更新 currentSum.f0 += 1; currentSum.f1 += input.f1; // 更新状态值 sum.update(currentSum); // 如果count >=2 清空状态值,重新计算 if (currentSum.f0 >= 2) { out.collect(new Tuple2<>(input.f0, currentSum.f1 / currentSum.f0)); sum.clear(); } } @Override public void open(Configuration config) { ValueStateDescriptor<Tuple2<Long, Long>> descriptor = new ValueStateDescriptor<>( "average", // 状态名称 TypeInformation.of(new TypeHint<Tuple2<Long, Long>>() {}), // 状态类型 Tuple2.of(0L, 0L)); // 状态默认值 sum = getRuntimeContext().getState(descriptor); }} // ...env.fromElements(Tuple2.of(1L, 3L), Tuple2.of(1L, 5L), Tuple2.of(1L, 7L), Tuple2.of(1L, 4L), Tuple2.of(1L, 2L)) .keyBy(0) .flatMap(new CountWindowAverage()) .print(); // the printed output will be (1,4) and (1,5)
欢迎点赞+收藏+转发朋友圈素质三连
文章不错?点个【在看】吧!