带有键值的状态(Keyed State)和算子状态(Operator State)
Flink中共有两类状态(state):带有键值的状态(Keyed State)
和 算子状态(Operator State)
。
带有键值的状态(Keyed State)
带有键值的状态(Keyed State)总是和键(keys)有关并且只能用在带有键值的流(KeyedStream)
的函数和操作符中。
你可以认为带有键值的状态(Keyed State)像算子状态(Operator State)一样已经被 分区或者分片过了,每一个键值有唯一的一个状态(state)划分。每一个带有键值的状态(Keyed-state)逻辑上与 唯一的一对<并行的操作符实例,键值>(
带有键值的状态(Keyed State)接着被放在键组(Key Groups)中进行管理。键组是Flink用来 重新分布带有键值的状态(Keyed State)的原子单元;并行化数量被设为了多少,就有多少个键值组。 在执行过程中每一个并行的带有键值的算子对应一个或多个键值组中的键值。
算子状态(Operator State)
用算子状态(Operator State)的时候,每一个算子状态(operator state)对 应一个并行的操作符实例。
Kafka 连接器Kafka Connector 是在Flink中使用算子状态(operator state)的一个很好的 实例。每一个并行的Kafka消费组(Kafka consumer)里保存着那些主题分片(topic partition)的map和其偏移量(offsets)来作为算子状态(Operator state).
算子状态(Operator state)接口支持在并行化情况改变的时候对并行算子状态(state)进行重分布。我们还有不同的模式来支持重分布。
原生的与托管的状态(Raw and Managed State)
键值状态 (Keyed State) 和 算子状态(Operator State) 存在两种形式: 托管的(managed) and 原生的(raw).
托管的状态(managed state)表现为由Flink内部控制的一些数据结构,例如内部的哈希表,或者 RocksDB。对于”值状态(ValueState)”和”列表状态(Liststate)”这样的,Flink先内部对这些状态(State)进行编码,然后把它们写到检查点中。
原生的状态 (Raw state) 指的是算子可以保存着自己的数据结构的状态(state)。当被写成检查点的时候,它们只写入一些比特序列串,Flink对这些状态(State)的内部数据结构一无所知,只能看到那些原生的比特序列。
Flink提供的所有的数据流函数都可以运用托管的状态(managed state),但是原生的状态(raw state)接口只能在实现算子的时候使用。比起原生的状态(raw state),我们更加推荐使用托管的状态(managed state),因为在并行化的情况改变的时候,Flink可以自动的重新分布托管的状态(managed state),并且也能够更好的做到内存管理。
托管的键值状态(Managed Keyed State)接口提供了通过当前输入元素的键值来存取使用各种不同类型的状态(state)的方法。 这就意味着这种类型的状态(state)只能在带有键值的流(KeyedStream)
中使用,我们可以通过stream.keyBy(…)
来创建它。
现在,我们先来看看有哪些可供我们使用的不同类型的状态(state),然后再看如何在程序中使用它们。可以使用的状态(state)主要有以下几种:
ValueState
: 这里面存储着一个可以被更新检索的状态(state)(与之前提过的输入元素的键值相关,所以对于每一个键值都 有可能有一个相应的状态(state)的值),这个值可以用update(T)
来设置,用T value()
来检索。ListState
: 这里存储着一个元素的列表。您可以向列表中增补元素,也可以用一个Iterable
来检索当前所有的元素。 增补元素您可以使用add(T)
, 迭代的检索元素您可以使用Iterable
。get() ReducingState
: 这里面存储着一个单一的数值,它代表了所有被加到这个状态(state)里面的值共同聚合后的值。 它的接口和ListState
是一样的,只是元素用add(T)
加进状态(state),用ReduceFunction
来做具体的聚合操作。FoldingState
: 这里存储着一个单一的数值,代表了所有被加到这个状态(state)里面的元素共同聚合后的值,与ReducingState
不同的是,这个聚合的值的类型可以与被加进状态(state)中的值的类型不同。它的接口和ListState
是 一样的,元素用add(T)
加进状态(state),但是用FoldFunction
来做具体的聚合操作。MapState
: 这里存储着一个mapping的列表,你可以将一个个键值对加入状态(state)然后用Iterable
来检索当前存储的 所有的mappings,您可以使用put(UK, UV)
或者putAll(Map
加入键值对,与键对应的数值可以用) get(UK)
检索, 迭代的mappings,键和值可以分别用entries()
,keys()
和values()
检索。
所有类型的状态(state)都可以用clear()
来清除当前有效键(例如,当前输入元素的键)对应的状态(state)。
您需要记住的第一点是您只能在状态(state)接口中使用这些状态对象,它们并非必须存储在内部也有可能在磁盘或者其他地方重置。 第二点是您得到的状态(state)的值是依赖于它所对应的键的,因此如果键不同的话那您在每一个函数调用中所得到的值也会不同。
为了对状态(state)进行操作,您必须要创建一个 StateDescriptor
,它保存了这个状态(state)的名称,(接下来您会看到您 可以创建多个状态(state),但是它们必须有相同的名字,以便你今后关联它们),还有状态(state)保存着的值的类型,有可能还有和一个 用户自定义的函数,比如说一个 ReduceFunction
。您可以根据您想检索的状态(state)的类型,来决定您是创建一个ValueStateDescriptor
, 还是一个ListStateDescriptor
,或者是ReducingStateDescriptor
或者FoldingStateDescriptor
或者 MapStateDescriptor
。
您可以通过RuntimeContext
来存取状态(state),所以它只能用在rich functions中。请点击这里 来获得更多信息,我们也先看一个小例子。 RichFunction
中的 RuntimeContext
有以下几个方法来存取状态(state):
ValueState
getState(ValueStateDescriptor ) ReducingState
getReducingState(ReducingStateDescriptor ) ListState
getListState(ListStateDescriptor ) FoldingState
getFoldingState(FoldingStateDescriptor ) MapState
getMapState(MapStateDescriptor )
这里是一个FlatMapFunction
的例子,让我们看看这几部分是如何协调工作的:
package state;
import org.apache.flink.api.common.functions.RichFlatMapFunction;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
public class CountWindowAverage extends RichFlatMapFunction, Tuple2> {
/**
* 保存处理ValueState,第一项为计数值,第二项为不断累加的和。
*/
private transient ValueState> sum;
@Override
public void flatMap(Tuple2 input, Collector> out) throws Exception {
// 存取状态值
Tuple2 currentSum = sum.value();
// 更新计数值
currentSum.f0 += 1;
// 将输入的元组值累计到第二项中
currentSum.f1 += input.f1;
// 更新状态
sum.update(currentSum);
// 如果计数值达到2,计算平均数,清除状态值
if (currentSum.f0 >= 2) {
out.collect(new Tuple2<>(input.f0, currentSum.f1 / currentSum.f0));
sum.clear();
}
}
@Override
public void open(Configuration config) {
ValueStateDescriptor> descriptor =
new ValueStateDescriptor<>(
"average", // 状态名
TypeInformation.of(new TypeHint>() {}), // 状态类型信息
Tuple2.of(0L, 0L)); // 默认的状态值
sum = getRuntimeContext().getState(descriptor);
}
public static void main(String[] args) {
//在这里我们可以应用于数据流程序(假定我们有一个StreamExecutionEnvironment环境)
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
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();
// 输出将为(1,4)和(1,5)
}
}
参考:
http://flink.iteblog.com/dev/stream/state.html