Flink架构体系中,最重要的特性之一便是有状态计算。
状态计算一般是指: 在程序计算过程中,在程序内部储存计算所产生的中间结果,并提供给后续Function或者算子计算结果使用。
Flink有两种基本类型的State: Keyed State 和 Operator State(Non-Keyed State)。
是一种和Key相关的一种State,只能用于KeyedStream类型数据类型所对应的Functions和Operator上。
Operator State和并行的算子实例绑定,与数据元素中的Key无关。Operator State支持 当算子实例并行度发生变化时自动重新分配状态数据。
托管状态(Manager State)
由Flink Runtime中控制和管理状态数据,并将状态数据转换为内存Hash tables或RocksDB的对象存储,然后将这些状态数据通过内部的接口持久化到CheckPoints中,任务异常时可以通过这些状态数据恢复任务。
原生状态(Row State)
由算子自己管理数据结构,当触发CheckPoint过程中,Flink并不知道状态数据内部的数据结构,只是将数据转换成bytes数据存储在CheckPoints中,当从CheckPoint恢复任务是,算子自己再反序列化出状态的数据结构。
上述的State对象,仅仅用于与状态进行交互(更新,删除,清空等),而真正的状态值,有可能是存在内存、磁盘、或者其他分布式存储系统中。相当于我们只是持有了这个状态的句柄。实际上这些状态有三种存储方式:
MemoryStateBackend
FsStateBackend
RockDBStateBackend
package com.jerome.flink.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.java.tuple.Tuple2;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
/**
* This is Description
*
* @author Jerome丶子木
* @date 2019/12/25
*/
public class ValueStateTest {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource> inputStream = env.fromElements(new Tuple2(2, 21L), new Tuple2(4, 1L), new Tuple2(5, 4L));
SingleOutputStreamOperator> leastValue = inputStream.keyBy(0).flatMap(new RichFlatMapFunction, Tuple3>() {
private ValueState leastValueState;
@Override
public void open(Configuration parameters) throws Exception {
//创建ValueStateDescriptor,定意状态名称为leastValue,并指定数据类型
ValueStateDescriptor leastValueStateDescriptor = new ValueStateDescriptor<>("leastValue", Long.class);
//通过getRuntimeContext.getState获取State
leastValueState = getRuntimeContext().getState(leastValueStateDescriptor);
}
@Override
public void flatMap(Tuple2 value, Collector> out) throws Exception {
//通过value方法从leastValueState中获取最小值
Long leastValue = leastValueState.value();
if (leastValue != null) {
//如果当前指标大于最小值,则直接输出数据元素和最小值
if (value.f1 > leastValue) {
out.collect(new Tuple3<>(value.f0, value.f1, leastValue));
} else {
//如果当前指标小于最小值,则更新状态中的最小值
leastValueState.update(value.f1);
//将当前数据中的指标作为最小值输出
out.collect(new Tuple3<>(value.f0, value.f1, value.f1));
}
}else {
//如果状态中的值是空,则更新状态中的最小值
leastValueState.update(value.f1);
//将当前数据中的指标作为最小值输出
out.collect(new Tuple3<>(value.f0, value.f1, value.f1));
}
}
});
leastValue.print();
env.execute("ValueStateTest");
}
}
CheckPoint可以理解为: 将State状态数据持久化,注意这个CheckPoint是在同一时间点 Task/Operator的状态的全局快照。
CheckPoint是Flink在输入的数据集上间隔性的生成checkpoint barrier,并通过barrier将时间间隔段内的数据划分到相应的CheckPoint中。一旦Flink程序意外崩溃时,重新运行程序时可以有选择的从这些快照中恢复所有算子之前的状态,从而保证数据一致性。
Apache Flink 进阶(三):Checkpoint 原理剖析与应用实践
下面阐述了state的存储,state是checkpoint进行持久化的主要角色。
下图阐释了目前 Flink 内置的三类 state backend,其中MemoryStateBackend和FsStateBackend在运行时都是存储在 java heap 中的,只有在执行 Checkpoint 时,FsStateBackend才会将数据以文件格式持久化到远程存储上。而RocksDBStateBackend则借用了 RocksDB(内存磁盘混合的 LSM DB)对 state 进行存储。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lEGH7QPv-1578641707672)(http://note.youdao.com/yws/res/22226/C34B91AB387C4CA5A2218BE5588429EA)]
对于HeapKeyedStateBackend , 有两种实现:
特别在 MemoryStateBackend 内使用HeapKeyedStateBackend时,Checkpoint 序列化数据阶段默认有最大 5 MB 数据的限制
MamoryStateBackend
state 数据保存在java堆内存中,执行checkpoint的时候,会把state的快照数据保存到jobmanager的内存中,基于内存的statebackend在生产环境不建议使用。
FsStateBackend
state 数据保存在TaskManager的内存中,执行checkpoint的时候,会把state的快照数据保存到配置的文件系统中,可以使hdfs等分布式文件系统。
对于RocksDBKeyedStateBackend,每个 state 都存储在一个单独的 column family 内,其中 keyGroup,Key 和 Namespace 进行序列化存储在 DB 作为 key。
uri(一般是HDFS),在做checkpoint的时候,会把本地的数据直接复制到filesystem中,failover的时候从filesystem中恢复到本地。
RocksDB克服了state受内存限制的缺点,同时又能够持久化到远端文件系统中,比较适合在生产中使用。
下图左侧是CheckPoint Coordinator,是整个CheckPoint的发起者,中间是由两个source,一个sink组成的Flink作业,最右侧的是持久化存储,在大部分用户场景中对应HDFS。
为了实现 EXACTLY ONCE 语义,Flink 通过一个 input buffer 将在对齐阶段收到的数据缓存起来,等对齐完成之后再进行处理。而对于 AT LEAST ONCE 语义,无需缓存收集到的数据,会对后续直接处理,所以导致 restore 时,数据可能会被多次处理。下图是官网文档里面就 Checkpoint align 的示意图:
需要特别注意的是,Flink 的 Checkpoint 机制只能保证 Flink 的计算过程可以做到 EXACTLY ONCE,端到端的 EXACTLY ONCE 需要 source 和 sink 支持。