Flink是一个分布式的流处理引擎,而流处理的其中一个特点就是7X24。那么,如何保障Flink作业的持续运行呢?
Flink的内部会将应用状态(state)存储到本地内存或者嵌入式的kv数据库中,由于采用的是分布式架构,Flink需要对本地生成的状态进行持久化存储,以避免因应用或者节点机器故障等原因导致数据的丢失。
Flink是通过checkpoint(检查点)的方式将状态写入到远程的持久化存储,从而就可以实现不同语义的结果保障。
什么是状态
我们看两段程序,第一段是spark的wordcount程序:
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
object WordCount {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
val ssc = new StreamingContext(conf, Seconds(5))
val lines = ssc.socketTextStream("localhost", 9999)
val words = lines.flatMap(_.split(" "))
val pairs = words.map(word => (word, 1))
val wordCounts = pairs.reduceByKey(_ + _)
wordCounts.print()
ssc.start()
ssc.awaitTermination()
}
}
我们输入两条数据
在使用Spark进行词频统计时,当前的统计结果不受历史统计结果的影响,只计算接收的当前数据的结果,这个就可以理解为无状态的计算。
然后来测试Flink程序:
package com.ts.flink;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
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;
public class SocketWordCount {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment().setParallelism(1);
DataStreamSource<String> streamSource = env.socketTextStream("localhost", 7777);
SingleOutputStreamOperator<Tuple2<String,Integer>> words = streamSource.flatMap(new FlatMapFunction<String, Tuple2<String,Integer>>() {
@Override
public void flatMap(String value, Collector<Tuple2<String,Integer>> out) throws Exception {
String[] splits = value.split("\\s");
for (String word : splits) {
out.collect(Tuple2.of(word, 1));
}
}
});
words.keyBy(0).sum(1).print();
env.execute("WC");
}
}
Flink的程序中,可以看出当第二次词频统计时,把第一次的结果值也统计在了一起,即Flink把上一次的计算结果保存在了状态里,第二次计算的时候会先拿到上一次的结果状态,然后结合新到来的数据再进行计算,这就可以理解成有状态的计算,如下图所示。
状态类型
Flink提供了两种基本类型的状态:分别是 Keyed State(键控状态)
和Operator State(算子状态)
。根据不同的状态管理方式,每种状态又有两种存在形式,分别为:managed(托管状态)
和raw(原生状态)
。
Operator State和Keyed State的区别
Operator State | Keyed State | |
---|---|---|
使用范围 | 可以用于所有算子,常用于Source阶段。一个算子对应一个state。 | 只能用于KeyedStream算子,每个key对应一个state。 |
扩缩容模式 | 并发改变时,可以使用多种方式进行重新分配,使用ListState进行均匀分配,使用BroadcastState把全部状态拷贝到新任务上。 | Flink会把键值分为不同的Key Group,并发改变时,flink会以Key Group为单位将键值分配给不同的任务。 |
访问方式 | 实现CheckpointedFunction或者ListCheckpoint的接口。 | 实现Rich Function,通过getRuntimeContext()返回的RuntimeContext进行获取。 |
数据结构 | ListState、BroadcastState、Union list state | ValueState、ListState、ReducingState、AggregatingState、MapState。 |
managed state和raw state的区别
managed state | raw state | |
---|---|---|
状态管理方式 | Flink runtime托管,自动存储、自动恢复、内存管理上有优化 | 用户自己管理,需要自己序列化 |
数据结构 | Flink提供常用的数据结构:ListState、MapState、ValueState等 | 字节数组:byte[] |
使用场景 | 大多数情况下均可使用 | 自定义Operator |
Operator State
算子状态的作用范围限定为算子任务。这意味着由同一并行任务所处理的所有数据都可以访问到相同的状态,状态对于同一任务而言是共享的。算子状态不能由相同或不同算子的另一个任务访问。
Operator State是一种non-keyed state,与并行的操作算子实例相关联,例如在Kafka Connector中,每个Kafka消费端算子实例都对应到Kafka的一个分区中,维护Topic分区和Offsets偏移量作为算子的Operator State。在Flink中可以实现ListCheckpointed接口或者CheckpointedFunction 接口来实现一个Operator State。
ListCheckpointed接口实例:
package com.ts.flink;
import org.apache.flink.api.common.functions.RichFlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.streaming.api.checkpoint.ListCheckpointed;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
import java.util.Collections;
import java.util.List;
/**
* 统计用户购买行为的数量
*/
public class ListCheckpointedExample {
private static class UserBehaviorCnt extends RichFlatMapFunction<Tuple3<Long, String, String>, Tuple2<String, Long>> implements ListCheckpointed<Long> {
private Long userBuyBehaviorCnt = 0L;
@Override
public void flatMap(Tuple3<Long, String, String> value, Collector<Tuple2<String, Long>> out) throws Exception {
if(value.f1.equals("buy")){
userBuyBehaviorCnt ++;
out.collect(Tuple2.of("buy",userBuyBehaviorCnt));
}
}
/**
* 获取某个算子实例的当前状态,该状态包括该算子实例之前被调用时的所有结果
* 以列表的形式返回一个函数状态的快照
* Flink触发生成检查点时调用该方法
* @param checkpointId checkpoint的ID,是一个唯一的、单调递增的值
* @param timestamp Job Manager触发checkpoint时的时间戳
* @return 返回一个operator state list,如果为null时,返回空list
* @throws Exception
*/
@Override
public List<Long> snapshotState(long checkpointId, long timestamp) throws Exception {
//返回单个元素的List集合,该集合元素是用户购买行为的数量
return Collections.singletonList(userBuyBehaviorCnt);
}
/**
* 初始化函数状态时调用,可能是在作业启动时或者故障恢复时
* 根据提供的列表恢复函数状态
* 注意:当实现该方法时,需要在RichFunction#open()方法之前调用该方法
* @param list 被恢复算子实例的state列表 ,可能为空
* @throws Exception
*/
@Override
public void restoreState(List<Long> list) throws Exception {
// 在进行扩缩容之后,进行状态恢复,需要把其他subtask的状态加在一起
for (Long cnt : list) {
userBuyBehaviorCnt += 1;
}
}
}
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment().setParallelism(1);
// 模拟数据源[userId,behavior,product]
DataStreamSource<Tuple3<Long, String, String>> userBehaviors = env.fromElements(
Tuple3.of(1L, "buy", "iphone"),
Tuple3.of(1L, "cart", "huawei"),
Tuple3.of(1L, "buy", "logi"),
Tuple3.of(1L, "fav", "oppo"),
Tuple3.of(2L, "buy", "huawei"),
Tuple3.of(2L, "buy", "onemore"),
Tuple3.of(2L, "fav", "iphone"));
userBehaviors
.flatMap(new UserBehaviorCnt())
.print();
env.execute("ListCheckpointedExample");
}
}
----------
(buy,1)
(buy,2)
(buy,3)
(buy,4)
CheckpointedFunction 接口实例:
package com.ts.flink;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
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.Tuple3;
import org.apache.flink.runtime.state.FunctionInitializationContext;
import org.apache.flink.runtime.state.FunctionSnapshotContext;
import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
public class CheckpointFunctionExample {
private static class UserBehaviorCnt implements CheckpointedFunction, FlatMapFunction<Tuple3<Long, String, String>, Tuple3<Long, Long, Long>> {
// 统计每个operator实例的用户行为数量的本地变量
private Long opUserBehaviorCnt = 0L;
// 每个key的state,存储key对应的相关状态
private ValueState<Long> keyedCntState;
// 定义operator state,存储算子的状态
private ListState<Long> opCntState;
@Override
public void flatMap(Tuple3<Long, String, String> value, Collector<Tuple3<Long, Long, Long>> out) throws Exception {
if (value.f1.equals("buy")) {
// 更新算子状态本地变量值
opUserBehaviorCnt += 1;
Long keyedCount = keyedCntState.value();
// 更新keyedstate的状态 ,判断状态是否为null,否则空指针异常
keyedCntState.update(keyedCount == null ? 1L : keyedCount + 1 );
// 结果输出
System.out.println(value);
out.collect(Tuple3.of(value.f0, keyedCntState.value(), opUserBehaviorCnt));
}
}
/**
* 会在生成检查点之前调用
* 该方法的目的是确保检查点开始之前所有状态对象都已经更新完毕
* @param context 使用FunctionSnapshotContext作为参数
* 从FunctionSnapshotContext可以获取checkpoint的元数据信息,
* 比如checkpoint编号,JobManager在初始化checkpoint时的时间戳
* @throws Exception
*/
@Override
public void snapshotState(FunctionSnapshotContext context) throws Exception {
// 使用opUserBehaviorCnt本地变量更新operator state
opCntState.clear();
opCntState.add(opUserBehaviorCnt);
}
/**
* 在创建checkpointedFunction的并行实例时被调用,
* 在应用启动或者故障重启时触发该方法的调用
* @param context 传入FunctionInitializationContext对象,
* 可以使用该对象访问OperatorStateStore和 KeyedStateStore对象,
* 这两个对象可以获取状态的句柄,即通过Flink runtime来注册函数状态并返回state对象
* 比如:ValueState、ListState等
* @throws Exception
*/
@Override
public void initializeState(FunctionInitializationContext context) throws Exception {
// 通过KeyedStateStore,定义keyedState的StateDescriptor描述符
ValueStateDescriptor valueStateDescriptor = new ValueStateDescriptor("keyedCnt", TypeInformation.of(new TypeHint<Long>() {
}));
// 通过OperatorStateStore,定义OperatorState的StateDescriptor描述符
ListStateDescriptor opStateDescriptor = new ListStateDescriptor("opCnt", TypeInformation.of(new TypeHint<Long>() {
}));
// 初始化keyed state状态值
keyedCntState = context.getKeyedStateStore().getState(valueStateDescriptor);
// 初始化operator state状态
opCntState = context.getOperatorStateStore().getListState(opStateDescriptor);
// 初始化本地变量operator state
for (Long state : opCntState.get()) {
opUserBehaviorCnt += state;
}
}
}
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment().setParallelism(1);
// 模拟数据源[userId,behavior,product]
DataStreamSource<Tuple3<Long, String, String>> userBehaviors = env.fromElements(
Tuple3.of(1L, "buy", "iphone"),
Tuple3.of(1L, "cart", "huawei"),
Tuple3.of(1L, "buy", "logi"),
Tuple3.of(1L, "fav", "oppo"),
Tuple3.of(2L, "buy", "huawei"),
Tuple3.of(2L, "buy", "onemore"),
Tuple3.of(2L, "fav", "iphone"));
userBehaviors
.keyBy(0)
.flatMap(new UserBehaviorCnt())
.print();
env.execute("CheckpointFunctionExample");
}
}
----------
(1,buy,iphone)
(1,1,1)
(1,buy,logi)
(1,2,2)
(2,buy,huawei)
(2,1,3)
(2,buy,onemore)
(2,2,4)
Keyed State
Keyed State只能由作用在KeyedStream上面的函数使用,该状态与某个key进行绑定,即每一个key对应一个state。Keyed State按照key进行维护和访问的,Flink会为每一个Key都维护一个状态实例,该状态实例总是位于处理该key记录的算子任务上,因此同一个key的记录可以访问到一样的状态。如下图所示,可以通过在一条流上使用keyBy()方法来生成一个KeyedStream。Flink提供了很多种keyed state,具体如下:
-
ValueState用于保存类型为T的单个值。用户可以通过ValueState.value()来获取该状态值,通过ValueState.update()来更新该状态。使用
ValueStateDescriptor
来获取状态句柄。- get 操作: ValueState.value()
- set 操作: ValueState.update(T value)
-
ListState用于保存类型为T的元素列表,即key的状态值是一个列表。用户可以使用ListState.add()或者ListState.addAll()将新元素添加到列表中,通过ListState.get()访问状态元素,该方法会返回一个可遍历所有元素的Iterable对象,注意ListState不支持删除单个元素,但是用户可以使用update(List values)来更新整个列表。使用
ListStateDescriptor
来获取状态句柄。- ListState.add(T value)
- ListState.addAll(List values)
- ListState.get()返回 Iterable
- ListState.update(List values)
-
MapState<K, V>用于保存一组key、value的映射,类似于java的Map集合。用户可以通过get(UK key)方法获取key对应的状态,可以通过put(UK k,UV value)方法添加一个键值,可以通过remove(UK key)删除给定key的值,可以通过contains(UK key)判断是否存在对应的key。使用
MapStateDescriptor
来获取状态句柄。- MapState.get(UK key)
- MapState.put(UK key, UV value)
- MapState.contains(UK key)
- MapState.remove(UK key)
-
ReducingState 调用add()方法添加值时,会立即返回一个使用ReduceFunction聚合后的值,用户可以使用ReducingState.get()来获取该状态值。使用
ReducingStateDescriptor
来获取状态句柄。 -
AggregatingState<I, O> 与ReducingState类似,不同的是它使用的是AggregateFunction来聚合内部的值,AggregatingState.get()方法会计算最终的结果并将其返回。使用
AggregatingStateDescriptor
来获取状态句柄。
举一个MapState的例子:
package com.ts.flink;
import org.apache.flink.api.common.functions.RichFlatMapFunction;
import org.apache.flink.api.common.state.MapState;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
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.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
public class MapStateExample {
//统计每个用户每种行为的个数
public static class UserBehaviorCnt extends RichFlatMapFunction<Tuple3<Long, String, String>, Tuple3<Long, String, Integer>> {
//定义一个MapState句柄
private transient MapState<String, Integer> behaviorCntState;
// 初始化状态
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
MapStateDescriptor<String, Integer> userBehaviorMapStateDesc = new MapStateDescriptor<>(
"userBehavior", // 状态描述符的名称
TypeInformation.of(new TypeHint<String>() {}), // MapState状态的key的数据类型
TypeInformation.of(new TypeHint<Integer>() {}) // MapState状态的value的数据类型
);
behaviorCntState = getRuntimeContext().getMapState(userBehaviorMapStateDesc); // 获取状态
}
@Override
public void flatMap(Tuple3<Long, String, String> value, Collector<Tuple3<Long, String, Integer>> out) throws Exception {
Integer behaviorCnt = 1;
// 如果当前状态包括该行为,则+1
if (behaviorCntState.contains(value.f1)) {
behaviorCnt = behaviorCntState.get(value.f1) + 1;
}
// 更新状态
behaviorCntState.put(value.f1, behaviorCnt);
out.collect(Tuple3.of(value.f0, value.f1, behaviorCnt));
}
}
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment().setParallelism(1);
// 模拟数据源[userId,behavior,product]
DataStreamSource<Tuple3<Long, String, String>> userBehaviors = env.fromElements(
Tuple3.of(1L, "buy", "iphone"),
Tuple3.of(1L, "cart", "huawei"),
Tuple3.of(1L, "buy", "logi"),
Tuple3.of(1L, "fav", "oppo"),
Tuple3.of(2L, "buy", "huawei"),
Tuple3.of(2L, "buy", "onemore"),
Tuple3.of(2L, "fav", "iphone"));
userBehaviors
.keyBy(0)
.flatMap(new UserBehaviorCnt())
.print();
env.execute("MapStateExample");
}
}
----------
(1,buy,1)
(1,cart,1)
(1,buy,2)
(1,fav,1)
(2,buy,1)
(2,buy,2)
(2,fav,1)
状态的生命周期管理
对于任何类型Keyed State都可以设定状态的生命周期(TTL),即状态的存活时间,以确保能够在规定时间内及时地清理状态数据。
如果配置了状态的TTL,那么当状态过期时,存储的状态会被清除。状态生命周期功能可以通过StateTtlConfig配置,然后将StateTtlConfig配置传入StateDescriptor中的enableTimeToLive方法中即可。
package com.ts.flink;
import org.apache.flink.api.common.functions.RichFlatMapFunction;
import org.apache.flink.api.common.state.MapState;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.api.common.state.StateTtlConfig;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
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.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
public class MapStateExample {
//统计每个用户每种行为的个数
public static class UserBehaviorCnt extends RichFlatMapFunction<Tuple3<Long, String, String>, Tuple3<Long, String, Integer>> {
//定义一个MapState句柄
private transient MapState<String, Integer> behaviorCntState;
/**
* newBuilder方法是必须要指定的,newBuilder中设定过期时间的参数。
* setUpdateType方法中传入的类型有三种:
* //禁用TTL,永远不会过期
* Disabled,
* // 创建和写入时更新TTL
* OnCreateAndWrite,
* // 与OnCreateAndWrite类似,但是在读操作时也会更新TTL
* OnReadAndWrite
* setStateVisibility方法中传入的类型有两种:
* //如果数据没有被清理,就可以返回
* ReturnExpiredIfNotCleanedUp,
* //永远不返回过期的数据,默认值
* NeverReturnExpired
*/
// 设置声明周期
StateTtlConfig ttlConfig = StateTtlConfig
// 指定TTL时长为10S
.newBuilder(Time.seconds(10))
// 只对创建和写入操作有效
.setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
// 不返回过期的数据
.setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
.build();
// 初始化状态
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
MapStateDescriptor<String, Integer> userBehaviorMapStateDesc = new MapStateDescriptor<>(
"userBehavior", // 状态描述符的名称
TypeInformation.of(new TypeHint<String>() {}), // MapState状态的key的数据类型
TypeInformation.of(new TypeHint<Integer>() {}) // MapState状态的value的数据类型
);
// 设置stateTtlConfig
userBehaviorMapStateDesc.enableTimeToLive(ttlConfig);
behaviorCntState = getRuntimeContext().getMapState(userBehaviorMapStateDesc); // 获取状态
}
@Override
public void flatMap(Tuple3<Long, String, String> value, Collector<Tuple3<Long, String, Integer>> out) throws Exception {
Integer behaviorCnt = 1;
// 如果当前状态包括该行为,则+1
if (behaviorCntState.contains(value.f1)) {
behaviorCnt = behaviorCntState.get(value.f1) + 1;
}
// 更新状态
behaviorCntState.put(value.f1, behaviorCnt);
out.collect(Tuple3.of(value.f0, value.f1, behaviorCnt));
}
}
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment().setParallelism(1);
// 模拟数据源[userId,behavior,product]
DataStreamSource<Tuple3<Long, String, String>> userBehaviors = env.fromElements(
Tuple3.of(1L, "buy", "iphone"),
Tuple3.of(1L, "cart", "huawei"),
Tuple3.of(1L, "buy", "logi"),
Tuple3.of(1L, "fav", "oppo"),
Tuple3.of(2L, "buy", "huawei"),
Tuple3.of(2L, "buy", "onemore"),
Tuple3.of(2L, "fav", "iphone"));
userBehaviors
.keyBy(0)
.flatMap(new UserBehaviorCnt())
.print();
env.execute("MapStateExample");
}
}
状态后端
什么是状态后端
状态的存储、访问以及维护,由一个可插入的组件决定,这个组件就是状态后端。
Flink提供了三种类型的状态后端,分别是基于内存的状态后(MemoryStateBackend、基于文件系统的状态后端(FsStateBackend)以及基于RockDB作为存储介质的RocksDB StateBackend。这三种类型的StateBackend都能够有效地存储Flink流式计算过程中产生的状态数据,在默认情况下Flink使用的是MemoryStateBackend,区别见下表。
MemoryStateBackend | FsStateBachend | RocksDBStateBackend | |
---|---|---|---|
存储方式 | state:TaskManager内存;CheckPoint:JobManager内存 | state:TaskManager内存;CheckPoint:外部文件系统(HDFS) | state:TaskManager内存中的RockDB(内存+磁盘);CheckPoint:外部文件系统(HDFS) |
使用场景 | 本地测试 | 分钟级窗口聚合、join,生产环境使用 | 超大状态作业,天级窗口聚合,生产环境使用 |
MemoryStateBackend
MemoryStateBackend将状态数据全部存储在JVM堆内存中,包括用户在使用DataStream API中创建的Key/Value State,窗口中缓存的状态数据,以及触发器等数据。MemoryStateBackend具有非常快速和高效的特点,但也具有非常多的限制,最主要的就是内存的容量限制,一旦存储的状态数据过多就会导致系统内存溢出等问题,从而影响整个应用的正常运行。同时如果机器出现问题,整个主机内存中的状态数据都会丢失,进而无法恢复任务中的状态数据。因此从数据安全的角度建议用户尽可能地避免在生产环境中使用MemoryStateBackend。Flink将MemoryStateBackend作为默认状态后端。
MemoryStateBackend比较适合用于测试环境中,并用于本地调试和验证,不建议在生产环境中使用。但如果应用状态数据量不是很大,例如使用了大量的非状态计算算子,也可以在生产环境中使MemoryStateBackend。
FsStateBachend
FsStateBackend是基于文件系统的一种状态后端,这里的文件系统可以是本地文件系统,也可以是HDFS分布式文件系统。创建FsStateBackend的构造函数如下:
FsStateBackend(Path checkpointDataUri, boolean asynchronousSnapshots)
其中path如果为本地路径,其格式为“file:///data/flink/checkpoints”,如果path为HDFS路径,其格式为“hdfs://nameservice/flink/checkpoints”。FsStateBackend中第二个Boolean类型的参数指定是否以同步的方式进行状态数据记录,默认采用异步的方式将状态数据同步到文件系统中,异步方式能够尽可能避免在Checkpoint的过程中影响流式计算任务。如果用户想采用同步的方式进行状态数据的检查点数据,则将第二个参数指定为True即可。
相比于MemoryStateBackend, FsStateBackend更适合任务状态非常大的情况,例如应用中含有时间范围非常长的窗口计算,或Key/value State状态数据量非常大的场景,这时系统内存不足以支撑状态数据的存储。同时FsStateBackend最大的好处是相对比较稳定,在checkpoint时,将状态持久化到像HDFS分布式文件系统中,能最大程度保证状态数据的安全性。
如果只是在单个应用中设置FsStateBachend,只需要在程序中声明:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new FsStateBackend("hdfs://namenode:40010/flink/checkpoints"));
如果要在整个集群中设置,那么就要在flink-conf.yaml中声明:
# 使用filesystem存储
state.backend: filesystem
# checkpoint存储路径
state.checkpoints.dir: hdfs://namenode:40010/flink/checkpoints
RocksDBStateBackend
与前面的状态后端不同,RocksDBStateBackend需要单独引入相关的依赖包。RocksDB 是一个 key/value 的内存存储系统,类似于HBase,是一种内存磁盘混合的 LSM DB。当写数据时会先写进write buffer(类似于HBase的memstore),然后在flush到磁盘文件,当读取数据时会现在block cache(类似于HBase的block cache),所以速度会很快。
RocksDBStateBackend在性能上要比FsStateBackend高一些,主要是因为借助于RocksDB存储了最新热数据,然后通过异步的方式再同步到文件系统中,但RocksDBStateBackend和MemoryStateBackend相比性能就会较弱一些。
需要注意 RocksDB 不支持同步的 Checkpoint,构造方法中没有同步快照这个选项。不过 RocksDB 支持增量的 Checkpoint,也是目前唯一增量 Checkpoint 的 Backend,意味着并不需要把所有 sst 文件上传到 Checkpoint 目录,仅需要上传新生成的 sst 文件即可。它的 Checkpoint 存储在外部文件系统(本地或HDFS),其容量限制只要单个 TaskManager 上 State 总量不超过它的内存+磁盘,单 Key最大 2G,总大小不超过配置的文件系统容量即可。对于超大状态的作业,例如天级窗口聚合等场景下可以使会用该状态后端。
如果只是在单个应用中设置RocksDBStateBackend,只需要在程序中声明:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new RocksDBStateBackend("hdfs://namenode:40010/flink/checkpoints"));
记得添加RocksDB依赖:
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-statebackend-rocksdb_2.11</artifactId>
<version>1.10.0</version>
<scope>provided</scope>
</dependency>
如果要在整个集群中设置,那么就要在flink-conf.yaml中声明:
# 操作RocksDBStateBackend的线程数量,默认值为1
state.backend.rocksdb.checkpoint.transfer.thread.num: 1
# 指定RocksDB存储状态数据的本地文件路径
state.backend.rocksdb.localdir: /var/rockdb/checkpoints
# 用于指定定时器服务的工厂类实现类,默认为“HEAP”,也可以指定为“RocksDB”
state.backend.rocksdb.timer-service.factory: HEAP