flink状态
用 DataStream API 编写的程序通常以各种形式保存状态:
- 在 Window 触发之前要么收集元素、要么聚合
- 转换函数可以使用 key/value 格式的状态接口来存储状态
- 转换函数可以实现 CheckpointedFunction 接口,使其本地变量具有容错能力
在介绍状态之前,先了解一下状态后端
状态后端:
什么是状态后端:
- 其实状态并不是由一个任务来进行维护和更新,而是由一个可插入的组件来控制状态的存储、访问、维护、更新、容灾,而这个组件就是状态后端。那么说白了,状态后端就是管理状态的。
- 状态具体存放在哪,取决于选择的状态后端
职责:
- 本地状态的管理(包括 Operator state、Keyed state、Map state、Reduce state、Window state)
- CheckPoint 管理,在启动 CheckPoint 机制时,状态会随着 CheckPoint 而持久化,以防止数据丢失、保障恢复时的一致性。 检查点的状态写入远程存储系统,进行状态存盘,在此过程中,状态的序列化与反序列化以及状态的存储与访问,都是由状态后端来进行管理
选择一个状态后端:
- MemoryStateBackend
- 内存级别的状态后端,会将状态作为内存中的对象进行管理,数据一对象的形式存储在TaskManager的JVM堆内存中,而将checkpoint存储在JobManager的堆内存中。默认每个独立的状态大小限制是 5 MB,可以在 MemoryStateBackend 的构造器中可以增加其大小(但是这个大小最大都不会超过 akka.framesize : 10485760b)。
- 优点:快速、低延迟
- 缺点:集群挂了,状态会丢失,checkpoint的数据也会丢失。状态都存取在TaskManager的JVM堆上,可能会受到JVM GC的影响
- 由于不稳定,一般用于开发环境,用于测试
- FSStateBackend
- 将checkpoint存到远程的持久化文件系统之上,少量的元数据信息存储到 JobManager 的内存中,而对于本地状态(运行中的状态数据),会将状态存在TaskManager的JVM堆内存中
- 优点:同时拥有内存级别的本地访问速度和更好的容错保证,性能更高
- 缺点:如果本地状态的数据比较大的话,会导致slot的内存溢出
- 高性能,一般用于生产环境
- RocksDBStateBackend
- RocksDB k-v格式的数据库
- 该状态后端适用于本地状态存储的数据量较大,而导致slot的内存不够的时候
- RocksDBStateBackend 将正在运行中的状态数据保存在 RocksDB 数据库中,RocksDB 数据库默认将数据存储在 TaskManager 的数据目录。 CheckPoint 时,整个 RocksDB 数据库被 checkpoint 到配置的文件系统目录中。 少量的元数据信息存储到 JobManager 的内存中
- 优点:访问速度也快,可以将状态持久化到硬盘上
- 缺点:由于持久化到硬盘上需要序列化与反序列化开销,所以性能较低,性能开销大
设置一个状态后端:
- env.setStateBackend(new MemoryStateBackend(MAX_MEM_STATE_SIZE, false)) 第一个参数是state的大小,第二个是是否开启异步快照,默认开启
- env.setStateBackend(new FSStateBackend(path, false)) 第一个参数是文件的路径,第二个是是否开启异步快照,FsStateBackend 默认使用异步快照来防止 CheckPoint 写状态时对数据处理造成阻塞
- env.setStateBackend(new RocksDBStateBackend()) 该模式只支持异步快照
- 如果用不到RocksDBStateBackend,强烈建议将managed memory 设为 0
状态:
键控状态(在键控数据流的基础上):
- ValueState<T>:值状态
- ListState<T>:将状态表示为一组数据的列表
- ReducingState<T>:聚合状态,将状态表示为一个用于聚合操作的列表,该状态存取的是数据聚合后的值,来了新数据之后相当于直接在此状态上进行聚合操作
- AggregatingState<IN, OUT>:聚合状态,和ReducingState相反的是,聚合类型可能与添加到状态的元素的类型不同
- MapState<UK, UV>:将状态表示为一组 k-v 键值对
- Flink为每个key维护一个状态实例,并且将具有相同键的数据都分区到同一个算子任务中,这个任务会维护和处理这个key的对应状态。每个key只维护自己key的状态,并且也只能访问到自己key的状态,即使不同的key进入到相同的分区后,在每个分区里面,还是会对不同的key进行状态的划分
广播状态:
一般用于动态改变的配置项,或者动态改变的维表信息(维表信息不要太大),要求算子的每个子任务都保留相同的状态,所有的子任务状态要求一致
// 一个 map descriptor,它描述了用于存储规则名称与规则本身的 map 存储结构
MapStateDescriptor<String, Rule> ruleStateDescriptor = new MapStateDescriptor<>(
"RulesBroadcastState",
BasicTypeInfo.STRING_TYPE_INFO,
TypeInformation.of(new TypeHint<Rule>() {}));
// 广播流,广播规则并且创建 broadcast state
BroadcastStream<Rule> ruleBroadcastStream = ruleStream
.broadcast(ruleStateDescriptor);
为了关联一个非广播流(keyed or non-keyed)与一个广播流(BroadcastStream),我们可以调用非广播流的方法connect()
DataStream<String> output = colorPartitionedStream
.connect(ruleBroadcastStream)
.process(
// KeyedBroadcastProcessFunction 中的类型参数表示:
// 1. key stream 中的 key 类型
// 2. 非广播流中的元素类型
// 3. 广播流中的元素类型
// 4. 结果的类型,在这里是 string
new KeyedBroadcastProcessFunction<Color, Item, Rule, String>() {
// 模式匹配逻辑
}
);
BroadcastProcessFunction 和 KeyedBroadcastProcessFunction
public abstract class BroadcastProcessFunction<IN1, IN2, OUT> extends BaseBroadcastProcessFunction {
public abstract void processElement(IN1 value, ReadOnlyContext ctx, Collector<OUT> out) throws Exception;
public abstract void processBroadcastElement(IN2 value, Context ctx, Collector<OUT> out) throws Exception;
}
public abstract class KeyedBroadcastProcessFunction<KS, IN1, IN2, OUT> {
public abstract void processElement(IN1 value, ReadOnlyContext ctx, Collector<OUT> out) throws Exception;
public abstract void processBroadcastElement(IN2 value, Context ctx, Collector<OUT> out) throws Exception;
public void onTimer(long timestamp, OnTimerContext ctx, Collector<OUT> out) throws Exception;
}
需要processBroadcastElement()负责处理广播流的元素,而processElement()负责处理另一种流的元素。
简单示例如下:
MapStateDescriptor<String,String> ruleStateDescriptor = new MapStateDescriptor<>(
"RulesBroadcastState",
BasicTypeInfo.STRING_TYPE_INFO,
BasicTypeInfo.STRING_TYPE_INFO
);
BroadcastStream<String> ruleSource = env.socketTextStream("192.168.110.12", 8888)
.broadcast(ruleStateDescriptor)
env.socketTextStream("192.168.110.12", 7777)
.connect(ruleSource)
.process(new BroadcastProcessFunction<String, String, String>()
// 负责处理另一种流的元素
@Override
public void processElement(String value, ReadOnlyContext ctx, Collector<String> out) throws Exception {
ReadOnlyBroadcastState<String, String> broadcastState = ctx.getBroadcastState(ruleStateDescriptor);
String s = broadcastState.get(value);
if (s != null){
out.collect(s);
}
// 负责处理广播流的元素
@Override
public void processBroadcastElement(String value, Context ctx, Collector<String> out) throws Exception {
BroadcastState<String, String> broadcastState = ctx.getBroadcastState(ruleStateDescriptor);
broadcastState.put(value,"broadcast");
})
.print()
;
键控流的话能够调用定时器

浙公网安备 33010602011771号