1.什么是状态

对于任何一个操作,都可以被看成是一个函数,比如y=f(x),如果对于同一个x的任何一次输入,得到的y都是相同的,则可以认为这个函数是无状态,否则,这个函数就是有状态的。Flink的一大特点就在于对状态的支持。

2.Keyed State和Operator State

Keyed State

Keyed State正如其名,总是和具体的key相关联,也只能在keyedStream的function和operator上使用。

Keyed State可以被当做是Operator State的一种特例,但是被分区或者分片的,对于某个key在某个分区上有唯一的状态。逻辑上,key state总是对应一个 <parallel-operator-instance, key>二元组,某种程度上,由于某个具体的Key总是属于某个具体的并行实例,这种情况下,也可以被简化认为是 <operator, key>。

Keyed State会被组织成Key Group。Key Group是可以被Flink用来进行重分布的最小单元,所以有多少个并发,就会有多少个Key Group。在执行过程中,每个keyed operator的并发实例会处理来自不同key的不同的Key Group。

Operator State

对Operator State而言,每个operator state都对应一个并行实例。Kafka Connector就是一个很好的例子。每个Kafka consumer的并行实例都会持有一份topic partition 和offset的map,这个map就是它的Operator State。

Operator State可以在并行度发生变化的时候将状态在所有的并行实例中进行重分布,并且提供了多种方式来进行重分布。

3.托管状态和非托管状态

Keyed State和Operator State都有两种存在形式,即托管状态和非托管状态。

托管状态可以使用flink runtime提供数据结构来实现,比如internal hash table和RocksDB。具体有ValueState,ListState等。Flink runtime会对这些状态进行编码并写入到checkpoints。

非托管状态使用用户自己的数据结构来实现。当做checkpoints时,非托管状态会以字节流的形式被写入checkpoints。Flink对托管状态的数据结构一无所知,只认为他们是一堆字节数组。

datastream的所有function都可以使用托管状态,非托管状态只能在实现operator的时候使用。相对于非托管状态,推荐使用托管状态,因为如果使用托管状态,Flink可以自动帮你进行状态重分布,也可以更好的做内存管理。

注意:如果你的托管状态需要特殊的序列化,目前Flink还不支持。

4.使用托管Keyed State

有如下的状态可以使用:

ValueState<T>:保持一个可以更新和获取的值(每个Key一个value),可以用来update(T)更新,用来T value()获取。

ListState<T>: 保持一个值的列表,用add(T) 或者 addAll(List<T>)来添加,用Iterable<T> get()来获取。

ReducingState<T>: 保持一个值,这个值是状态的很多值的聚合结果,接口和ListState类似,但是可以用相应的ReduceFunction来聚合。

AggregatingState<IN, OUT>:保持很多值的聚合结果的单一值,与ReducingState相比,不同点在于聚合类型可以和元素类型不同,提供AggregateFunction来实现聚合。

FoldingState<T, ACC>: 与AggregatingState类似,除了使用FoldFunction进行聚合。

MapState<UK, UV>: 保持一组映射,可以将kv放进这个状态,使用put(UK, UV) or putAll(Map<UK, UV>)添加,或者使用get(UK)获取。

所有类型的状态都有一个clear()方法,可以清除当前的状态。

注意:FoldingState已经不推荐使用,可以用AggregatingState来代替。

需要注意,如上的状态对象只用来和状态打交道,可能会被存储在磁盘或者其他地方。另外,你拿到的状态的值是与key相关的,所以在这个实例中拿到的值可能和别的实例中拿到的不一样。

要使用一个状态对象,需要先创建一个StateDescriptor,他包含了状态的名字,状态的值的类型,或许还有一个用户定义的函数,比如ReduceFunction。取决于你要使用的state,你可以创建ValueStateDescriptor或者 ListStateDescriptor或者 ReducingStateDescriptor或者 FoldingStateDescriptor或者 MapStateDescriptor。

状态只能通过RuntimeContext获取,所以只能是在rich functions里面。通过RuntimeContext可以用下述方法获取状态:

  • ValueState<T> getState(ValueStateDescriptor<T>)
  • ReducingState<T> getReducingState(ReducingStateDescriptor<T>)
  • ListState<T> getListState(ListStateDescriptor<T>)
  • AggregatingState<IN, OUT> getAggregatingState(AggregatingState<IN, OUT>)
  • FoldingState<T, ACC> getFoldingState(FoldingStateDescriptor<T, ACC>)
  • MapState<UK, UV> getMapState(MapStateDescriptor<UK, UV>)

如下是一个使用FlatMapFunction的例子:

public class CountWindowAverage extends RichFlatMapFunction<Tuple2<Long, Long>, Tuple2<Long, Long>> {

    /**
* The ValueState handle. The first field is the count, the second field a running sum.
*/
private transient ValueState<Tuple2<Long, Long>> sum; @Override
public void flatMap(Tuple2<Long, Long> input, Collector<Tuple2<Long, Long>> out) throws Exception { // access the state value
Tuple2<Long, Long> currentSum = sum.value(); // update the count
currentSum.f0 += 1; // add the second field of the input value
currentSum.f1 += input.f1; // update the state
sum.update(currentSum); // if the count reaches 2, emit the average and clear the state
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", // the state name
TypeInformation.of(new TypeHint<Tuple2<Long, Long>>() {}), // type information
Tuple2.of(0L, 0L)); // default value of the state, if nothing was set
sum = getRuntimeContext().getState(descriptor);
}
} // this can be used in a streaming program like this (assuming we have a StreamExecutionEnvironment env)
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)

  

5.使用托管Operator State

为了使用托管的Operator State,必须有一个有状态的函数,这个函数比较继承CheckpointedFunction或者ListCheckpointed<T extends Serializable>。

CheckpointedFunction

CheckpointedFunction有如下两个方法需要实现。

void snapshotState(FunctionSnapshotContext context) throws Exception;

void initializeState(FunctionInitializationContext context) throws Exception;

当checkpoint执行的时候,snapshotState()就会被调用。initializeState()会在第一次运行的时候被调用,或者从更早的checkpoint恢复的时候被调用。

目前,支持List类型的托管状态。状态被期望是一个可序列话的对象的List,彼此独立,这样便于重分布。不同的状态获取方式,会导致不同的重分布策略:

Even-split redistribution:每个operator会返回一组状态,所有的状态就变成了统一的状态。在重分布或者恢复的时候,一组状态会被按照并行度分为子组,每个operator会得到一个子组。

Union redistribution: 每个operator会返回一组状态,所有的状态一起组成了统一的状态。在重分布或者恢复的时候,每个operator都会得到所有的状态。

如下示例是一个有状态的SinkFunction使用CheckpointedFunction来在发送到外部之前缓存数据,使用了Even-split策略。

public class BufferingSink
implements SinkFunction<Tuple2<String, Integer>>,
CheckpointedFunction { private final int threshold; private transient ListState<Tuple2<String, Integer>> checkpointedState; private List<Tuple2<String, Integer>> bufferedElements; public BufferingSink(int threshold) {
this.threshold = threshold;
this.bufferedElements = new ArrayList<>();
} @Override
public void invoke(Tuple2<String, Integer> value) throws Exception {
bufferedElements.add(value);
if (bufferedElements.size() == threshold) {
for (Tuple2<String, Integer> element: bufferedElements) {
// send it to the sink
}
bufferedElements.clear();
}
} @Override
public void snapshotState(FunctionSnapshotContext context) throws Exception {
checkpointedState.clear();
for (Tuple2<String, Integer> element : bufferedElements) {
checkpointedState.add(element);
}
} @Override
public void initializeState(FunctionInitializationContext context) throws Exception {
ListStateDescriptor<Tuple2<String, Integer>> descriptor =
new ListStateDescriptor<>(
"buffered-elements",
TypeInformation.of(new TypeHint<Tuple2<String, Integer>>() {})); checkpointedState = context.getOperatorStateStore().getListState(descriptor); if (context.isRestored()) {
for (Tuple2<String, Integer> element : checkpointedState.get()) {
bufferedElements.add(element);
}
}
}
}

initializeState 有一个形参FunctionInitializationContext,用来初始化non-keyed状态容器。

注意上面代码中是如何初始化的,也是调用了StateDescriptor 来传递状态名字和状态的值的类型,如下:

ListStateDescriptor<Tuple2<String, Integer>> descriptor =
new ListStateDescriptor<>(
"buffered-elements",
TypeInformation.of(new TypeHint<Tuple2<Long, Long>>() {})); checkpointedState = context.getOperatorStateStore().getListState(descriptor);

状态存取方法的命名方式反映了重分布的方式,比如getUnionListState(descriptor),标志着使用list state并使用union 重分布。如果方法命名上没有反映重分布策略,比如getListState(descriptor),意味着最基础的even-split重分布策略会被使用。

初始化之后,使用isRestored()来判断是否是一个错误恢复。如果是,则需要执行错误分配的逻辑。

正如在上面的BufferingSink中所示,在状态初始化的时候恢复出来的ListState被保存在类变量中以便在snapshotState()中使用。然后ListState清空了上次checkpoint的对象,并填充了新的对象,以便做checkpoint。

再说一点,keyed state也可以在initializeState()中初始化,这个可以通过使用FunctionInitializationContext来实现。

ListCheckpointed

是一种受限的CheckpointedFunction,只支持List风格的状态和even-spit的重分布策略

List<T> snapshotState(long checkpointId, long timestamp) throws Exception;

void restoreState(List<T> state) throws Exception;

snapshotState()会返回一组对象给checkpoint,restoreState则需要在恢复的时候处理这一组对象。如果状态是不可分区的,则可以在snapshotState()中始终返回Collections.singletonList(MY_STATE)。

Stateful Source Functions

与其他operator相比,有状态的source需要更多的注意,为了使得状态的更新和结果的输出原子化,用户必须在source的context上加锁。

public static class CounterSource
extends RichParallelSourceFunction<Long>
implements ListCheckpointed<Long> { /** current offset for exactly once semantics */
private Long offset; /** flag for job cancellation */
private volatile boolean isRunning = true; @Override
public void run(SourceContext<Long> ctx) {
final Object lock = ctx.getCheckpointLock(); while (isRunning) {
// output and state update are atomic
synchronized (lock) {
ctx.collect(offset);
offset += 1;
}
}
} @Override
public void cancel() {
isRunning = false;
} @Override
public List<Long> snapshotState(long checkpointId, long checkpointTimestamp) {
return Collections.singletonList(offset);
} @Override
public void restoreState(List<Long> state) {
for (Long s : state)
offset = s;
}
}

或许有些operator想知道什么时候checkpoint全部做完了,可以参考使用org.apache.flink.runtime.state.CheckpointListener。

Flink之状态之状态获取的更多相关文章

  1. Apache Flink中的广播状态实用指南

    感谢英文原文作者:https://data-artisans.com/blog/a-practical-guide-to-broadcast-state-in-apache-flink 不过,原文最近 ...

  2. ListView在编辑状态下不能获取修改后的值,无法更新数据

    ListView在编辑状态下不能获取修改后的值,获取到的总是以前的值解决方法:在page_load事件里写: if(!IsPostBack) { ListViewBind(); } 原因:这涉及到as ...

  3. State Processor API:如何读取,写入和修改 Flink 应用程序的状态

    过去无论您是在生产中使用,还是调研Apache Flink,估计您总是会问这样一个问题:我该如何访问和更新Flink保存点(savepoint)中保存的state?不用再询问了,Apache Flin ...

  4. Flink 源码解析 —— 如何获取 ExecutionGraph ?

    https://t.zsxq.com/UnA2jIi 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Mac 上搭建 Flink 1.6. ...

  5. Flink 源码解析 —— 如何获取 JobGraph?

    JobGraph https://t.zsxq.com/naaMf6y 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Mac 上搭建 F ...

  6. Flink 源码解析 —— 如何获取 StreamGraph?

    StreamGraph https://t.zsxq.com/qRFIm6I 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Mac 上搭 ...

  7. 与众不同 windows phone (23) - Device(设备)之硬件状态, 系统状态, 网络状态

    原文:与众不同 windows phone (23) - Device(设备)之硬件状态, 系统状态, 网络状态 [索引页][源码下载] 与众不同 windows phone (23) - Devic ...

  8. Activity的保存状态和状态恢复

    Activity的保存状态和状态恢复 当系统内存不足时,系统会强制结束一些不可见的Activity以节省内存资源.在某些情况下,当被强制结束的Activity再次显示时会出现一些问题. 例如:一个AP ...

  9. Java线程池状态和状态切换

    摘要 介绍线程池的五种状态RUNNING.SHUTDOWN.STOP.TIDYING和TERMINATED,并简述五种状态之间的切换.   在类ThreadPoolExecutor中定义了一个成员变量 ...

随机推荐

  1. PHP (Yii2) 自定义业务异常类(可支持返回任意自己想要的类型数据)

    public function beforeAction($action) { return parent::beforeAction($action); } public function runA ...

  2. elasticsearch 5.x 系列之六 文档索引,更新,查询,删除流程

    一.elasticsearch index 索引流程 步骤: 客户端向Node1 发送索引文档请求 Node1 根据文档ID(_id字段)计算出该文档应该属于shard0,然后请求路由到Node3的P ...

  3. 在pythonanywhere上部署Django应用

    参考:https://tutorial.djangogirls.org/zh/deploy/ 资料讲的很明了,仅在设置上做几点补充. 1.设置相对路径 import os BASE_DIR = os. ...

  4. C++常量(const)的使用

    #include <iostream> using namespace std; class MyClass { public: int GetValue() const ; int Ge ...

  5. 006---Python基本数据类型--集合

    集合 .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px ...

  6. Windows Server 2012下手动配置IIS的文件夹访问权限

    当新建一个website的时候,一般情况下IIS对相应的物理文件夹的访问权限是不够的. 针对匿名认证(anonymous authentication)需要: 打开文件夹properties-> ...

  7. java web项目使用ant编译将不同的功能代码打包成jar,进而分局点将项目打包成不同的tar.gz包进而部署

    使用ant可以轻松的将一个项目分离代码,直接打包成不同需求的tar.gz包使用 1.build.properties (属性) version.num=1.0 #版本信息 2.build.xml (a ...

  8. java入门---基础语法&基础常识&编码规范&命名规范

        一个Java程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作.下面简要介绍下类.对象.方法和实例变量的概念. 对象:对象是类的一个实例,有状态和行为.例如,一条狗是一个对 ...

  9. OpenCV代码提取:transpose函数的实现

    OpenCV中的transpose函数实现图像转置,公式为: 目前fbc_cv库中也实现了transpose函数,支持多通道,uchar和float两种数据类型,经测试,与OpenCV3.1结果完全一 ...

  10. Messy Code in Windows Server 2008 R2 English Edition

          We always use Windows Server 2008 R2 English operation system. And it doesn't have any problem ...