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)
欢迎点赞+收藏+转发朋友圈素质三连
文章不错?点个【在看】吧! ?
标签:状态,updateStateByKey,Flink,state,State,key,Spark,状态值 来源: https://blog.51cto.com/u_9928699/2893372