有状态计算
有状态计算是流处理框架要实现的重要功能,复杂的流处理场景都需要记录状态;状态本质上是Fllink内部存储计算产生的中间结果或者说是流处理过程中需要记录的数据;实际上,Flink的状态是由算子的子任务来创建和管理的;
状态分类
托管状态(Manged State)
Flink管理,Flink负责存储、恢复和优化;具体由Flink Runtime托管,自动存储、自动恢复、自动扩展;可细分为两类
- Keyed State:适用分组后的keyStream的算子,每个key对应一个自己的状态;
- Operator State:作用于所有算子,每个算子子任务共享一个状态;
无论是Keyed State还是Operator State,Flink的状态都是基于本地的,即每个算子子任务维护着自身的状态,不能访问其他算子子任务的状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XN2g8g4k-1689169684582)(C:\Users\务本\AppData\Roaming\Typora\typora-user-images\image-20230613122735876.png)]
原生状态(Raw State)
开发者管理,需要自己进行序列化,数据类型为byte[]
Keyed State的使用
- 支持的数据类型
ValueState:
- 保存单个值.
- 每个有 key 有一个状态值.
- 设置使用update(T), 获取使用T value()
ListState:
- 保存元素列表.
- 添加元素: add(T) addAll(List< T >)
- 获取元素: Iterable< T > get()
- 覆盖所有元素: update(List< T >)
ReducingState:
- 存储单个值, 表示把所有元素的聚合结果添加到状态中. 与 ListState 类似, 但 是当使用 add(T)的时候 ReducingState 会使用指定的 ReduceFunction 进行聚合.
AggregatingState<IN, OUT>:
- 存储单个值. 与 ReducingState 类似, 都是进行聚合. 不同的是, AggregatingState 的聚合的结果和元素类型可以不一样.
MapState<UK, UV>:
- 存储键值对列表.
- 添加键值对: put(UK, UV) or putAll(Map<UK, UV>)
- 根据 key 获取值:get(UK)
- 获取所有: entries(), keys() and values()
- 检测是否为空: isEmpty()
使用示例:
package com.aikfk.flink.datastream.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.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
/**
* @author :caizhengjie
* @description:TODO
* @date :2021/3/31 4:04 下午
*/
public class KeyedStateCount {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<Tuple2<String,Long>> dataStream = env.fromElements(
Tuple2.of("a", 3L),
Tuple2.of("a", 5L),
Tuple2.of("b", 7L),
Tuple2.of("c", 4L),
Tuple2.of("c", 2L))
.keyBy(value -> value.f0)
.flatMap(new CountFunction());
dataStream.print();
env.execute("KeyedState");
}
static class CountFunction extends RichFlatMapFunction<Tuple2<String,Long>,Tuple2<String,Long>>{
// 定义状态ValueState
private ValueState<Tuple2<String,Long>> keyCount;
/**
* 初始化
* @param parameters
* @throws Exception
*/
@Override
public void open(Configuration parameters) throws Exception {
ValueStateDescriptor<Tuple2<String,Long>> descriptor =
new ValueStateDescriptor<Tuple2<String, Long>>("keycount",
TypeInformation.of(new TypeHint<Tuple2<String,Long>>() {}));
keyCount = getRuntimeContext().getState(descriptor);
}
@Override
public void flatMap(Tuple2<String, Long> input,
Collector<Tuple2<String, Long>> collector) throws Exception {
// 使用状态
Tuple2<String, Long> currentValue =
(keyCount.value() == null) ? new Tuple2<>("", 0L) : keyCount.value();
// 累加数据
currentValue.f0 = input.f0;
currentValue.f1 ++;
// 更新状态
keyCount.update(currentValue);
collector.collect(keyCount.value());
}
}
}
Operator State的使用
- 支持的数据类型
列表状态(List state)
将状态表示为一组数据的列表联合列表状态(Union list state)
也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保 存点(savepoint)启动应用程序时如何恢复。
一种是均匀分配(List state),另外一种是将所有 State 合并为全量 State 再分发给每个 实例(Union list state)。广播状态(Broadcast state)
是一种特殊的算子状态. 如果一个算子有多项任务,而它的每项任务状态又都相同,那 么这种特殊情况最适合应用广播状态。
package com.aikfk.flink.datastream.state;
import org.apache.flink.api.common.state.*;
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.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.DataStreamSink;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.SinkFunction;
import java.util.ArrayList;
import java.util.List;
/**
* @author :caizhengjie
* @description:TODO
* @date :2021/3/31 4:04 下午
*/
public class OperatorState {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSink<Tuple2<String,Long>> dataStream = env.fromElements(
Tuple2.of("a", 3L),
Tuple2.of("a", 5L),
Tuple2.of("b", 7L),
Tuple2.of("c", 4L),
Tuple2.of("c", 2L))
.keyBy(value -> value.f0)
.addSink(new BufferingSink());
env.execute("KeyedState");
}
static class BufferingSink implements SinkFunction<Tuple2<String,Long>>, CheckpointedFunction {
private ListState<Tuple2<String,Long>> listState;
private List<Tuple2<String,Long>> bufferedElements = new ArrayList<>();
@Override
public void initializeState(FunctionInitializationContext context) throws Exception {
ListStateDescriptor<Tuple2<String, Long>> descriptor =
new ListStateDescriptor<Tuple2<String, Long>>("bufferedSinkState",
TypeInformation.of(new TypeHint<Tuple2<String,Long>>() {}));
listState = context.getOperatorStateStore().getListState(descriptor);
if (context.isRestored()){
for (Tuple2<String, Long> element : listState.get()){
bufferedElements.add(element);
}
}
}
@Override
public void snapshotState(FunctionSnapshotContext context) throws Exception {
for (Tuple2<String, Long> element : bufferedElements){
listState.add(element);
}
}
@Override
public void invoke(Tuple2<String,Long> value, Context context) throws Exception {
bufferedElements.add(value);
System.out.println("invoke>>> " + value);
for (Tuple2<String,Long> element : bufferedElements){
System.out.println(Thread.currentThread().getId() + " >> " + element.f0 + " : " + element.f1);
}
}
}
}
CheckPoint机制
flink的错机制,定期对状态进行快照,当flink故障时,可以从存储的快照恢复,保证了本地状态不会丢失;
- 快照流程
①暂停处理新流入数据,将新数据缓存起来。②将算子子任务的本地状态数据复制到一个远程的持久化存储空间上。③继续处理新流入的数据,包括刚才缓存起来的数据
-
快照持久化策略
- MemoryStateBackend:保存在内存中,JobManager的堆上,默认方式
//设置存储空间大小,默认5M env.setStateBackend(new MemoryStateBackend(MAX_MEM_STATE_SIZE));
- FsStateBackend:保存在文件系统上,如HDFS以及包括AWS、阿里云等在内的云存储服务
// 使用HDFS作为State Backend env.setStateBackend(new FsStateBackend("hdfs://namenode:port/flink-checkpoints/chk-17/")); // 使用阿里云OSS作为State Backend env.setStateBackend(new FsStateBackend("oss://<your-bucket>/<object-name>")); // 使用AWS作为State Backend env.setStateBackend(new FsStateBackend("s3://<your-bucket>/<endpoint>")); // 关闭Asynchronous Snapshot env.setStateBackend(new FsStateBackend(checkpointPath, false));
- RocksDBStateBackend:存储在RocksDB上,也需要配置分布式存储空间地址
// 开启Incremental Checkpoint boolean enableIncrementalCheckpointing = true; env.setStateBackend(new RocksDBStateBackend(checkpointPath, enableIncrementalCheckpointing));
-
相关配置
参照官网https://nightlies.apache.org/flink/flink-docs-release-1.17/zh/docs/dev/datastream/fault-tolerance/checkpointing/
// 设置模式为精确一次 (这是默认值)
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 确认 checkpoints 之间的时间会进行 500 ms
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
// Checkpoint 必须在一分钟内完成,否则就会被抛弃
env.getCheckpointConfig().setCheckpointTimeout(60000);
// 允许两个连续的 checkpoint 错误
env.getCheckpointConfig().setTolerableCheckpointFailureNumber(2);
// 同一时间只允许一个 checkpoint 进行
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
// 使用 externalized checkpoints,这样 checkpoint 在作业取消后仍就会被保留
env.getCheckpointConfig().setExternalizedCheckpointCleanup(
ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
// 开启实验性的 unaligned checkpoints
env.getCheckpointConfig().enableUnalignedCheckpoints();
-
Flink重启
-
重启流程:①重启应用,在集群上重新部署数据流图。②从持久化存储空间上读取最近一次的Checkpoint数据,加载到各算子子任务上。③继续处理新流入的数据。
-
重启策略:固定延迟,失败率,不重启
- 固定延迟(Fixed Delay)策略:作业每次失败后,按照设定的时间间隔进行重启尝试,重启次数不会超过某个设定值。
#配置文件 restart-strategy: fixed-delay restart-strategy.fixed-delay.attempts: 3 restart-strategy.fixed-delay.delay: 10 s
#代码配置 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 开启Checkpoint env.enableCheckpointing(5000L); env.setRestartStrategy( RestartStrategies.fixedDelayRestart( 3, // 尝试重启次数 Time.of(10L, TimeUnit.SECONDS) // 两次重启之间的延迟为10秒 ));
- 失败率(Failure Rate)策略:计算一个时间段内作业失败的次数,如果失败次数小于设定值,继续重启,否则不重启。
#配置文件,5min小于三次 restart-strategy: failure-rate restart-strategy.failure-rate.max-failures-per-interval: 3 restart-strategy.failure-rate.failure-rate-interval: 5 min restart-strategy.failure-rate.delay: 10 s
- 不重启(No Restart)策略:不对作业进行重启。
restart-strategy: none
-
Exactly-Once保障
故障恢复与一致性保障,保障数据流的处理,每天数据只处理一次;实现机制
- 基于Checpoint故障恢复保证内部状态一致性;无法解决数据重发的问题。
- Source支持数据重发功能;通过状态记录offset,
- Sink采用幂等性写和事务写;
幂等写
任意多次向一个系统写入数据,只对目标产生一次结果;如向HashMap插入同一个二元组,第一次插入发生变化,后续插入不会该表HashMap结果;
像Cassandra、HBase和Redis这样的Key-Value数据库一般用来作为Sink,用以实现端到端的Exactly-Once保障。
**事务写 **
Flink事务写指Flink先把待输出的数据保存下来暂不祥外部系统提交,待CheckPoint结束,上下游算子的数据都一致时,再提交数据;实现方式由预写日志和两阶段提交两种方式;