Flink State 和 Fault Tolerance
什么是状态?
所谓的状态,其实指的是 Flink 程序的中间计算结果。Flink 支持了不同类型的状态,并且针对状态的持久化还提供了专门的机制和状态管理器。
flink状态的应用,比如:
-
When an application searches for certain event patterns, the state will store the sequence of events encountered so far. --复杂事件处理获取符合某一特定时间规则的事件
-
When aggregating events per minute/hour/day, the state holds the pending aggregates. --聚合计算中
-
When training a machine learning model over a stream of data points, the state holds the current version of the model parameters. --机器学习的模型训练
-
When historic data needs to be managed, the state allows efficient access to events that occurred in the past.--使用历史的数据进行计算
当我们在使用state的时候,应该先熟悉一下flink的state backends,state backends指定了state应该怎么保存以及保存到哪里。(state可以保存到jvm 的堆内存中也可以保存到堆外内存。当然也可以借助第三方存储,例如 Flink 已经实现的对 RocksDB 支持)
状态的类型
1. Keyed State
基于KeyedStream使用,Keyed State 是经过分区后的流上状态,每个 Key 都有自己的状态并且只有指定的 key 才能访问和更新自己对应的状态。
2. Operator State (or non-keyed state)
Operator State 可以用在所有算子上,每个算子子任务或者说每个算子实例共享一个状态,流入这个算子子任务的数据可以访问和更新这个状态。每个算子子任务上的数据共享自己的状态。Kafka Connector 是一个flink中使用Operator State的很好的例子,每个Kafka consumer都包含了topic partitions和offsets作为其Operator State
Operator State 的实际应用场景不如 Keyed State 多,一般来说它会被用在 Source 或 Sink 等算子上,用来保存流入数据的偏移量或对输出数据做缓存,以保证 Flink 应用的 Exactly-Once 语义。
Keyed State和Operator State可以以两种形式存在:分别是Raw State和Managed State
代码示例
public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 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(); env.execute("submit job"); } public static 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 { Tuple2<Long, Long> currentSum; // access the state value if (sum.value() == null) { currentSum = Tuple2.of(0L, 0L); } else { 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 sum = getRuntimeContext().getState(descriptor); } }
我们这里的输出条件为,每当第一个元素的和达到二,就把第二个元素的和与第一个元素的和相除,最后输出。我们直接运行,在控制台可以看到结果:
6> (1,4) 6> (1,5)
状态如何保存及恢复
Checkpoint
Flink 状态保存主要依靠 Checkpoint 机制,Checkpoint 会定时制作分布式快照,对程序中的状态进行备份。分布式快照是如何实现的可以参考入门一的内容,这里就不在阐述分布式快照具体是如何实现的。分布式快照 Checkpoint 完成后,当作业发生故障了如何去恢复?假如作业分布跑在 3 台机器上,其中一台挂了。这个时候需要把进程或者线程移到 active 的 2 台机器上,此时还需要将整个作业的所有 Task 都回滚到最后一次成功 Checkpoint 中的状态,然后从该点开始继续处理。如果要从 Checkpoint 恢复,必要条件是数据源需要支持数据重新发送。Checkpoint 恢复后, Flink 提供两种一致性语义,一种是恰好一次,一种是至少一次。在做 Checkpoint 时,可根据 Barries 对齐来判断是恰好一次还是至少一次,如果对齐,则为恰好一次,否则没有对齐即为至少一次。如果作业是单线程处理,也就是说 Barries 是不需要对齐的;如果只有一个 Checkpoint 在做,不管什么时候从 Checkpoint 恢复,都会恢复到刚才的状态;如果有多个节点,假如一个数据的 Barries 到了,另一个 Barries 还没有来,内存中的状态如果已经存储。那么这 2 个流是不对齐的,恢复的时候其中一个流可能会有重复。Checkpoint 通过代码的实现方法如下:
- 首先从作业的运行环境 env.enableCheckpointing 传入 1000,意思是做 2 个 Checkpoint 的事件间隔为 1 秒。Checkpoint 做的越频繁,恢复时追数据就会相对减少,同时 Checkpoint 相应的也会有一些 IO 消耗。
- 接下来是设置 Checkpoint 的 model,即设置了 Exactly_Once 语义,并且需要 Barries 对齐,这样可以保证消息不会丢失也不会重复。
- setMinPauseBetweenCheckpoints 是 2 个 Checkpoint 之间最少是要等 500ms,也就是刚做完一个 Checkpoint。比如某个 Checkpoint 做了 700ms,按照原则过 300ms 应该是做下一个 Checkpoint,因为设置了 1000ms 做一次 Checkpoint 的,但是中间的等待时间比较短,不足 500ms 了,需要多等 200ms,因此以这样的方式防止 Checkpoint 太过于频繁而导致业务处理的速度下降。
- setCheckpointTimeout 表示做 Checkpoint 多久超时,如果 Checkpoint 在 1min 之内尚未完成,说明 Checkpoint 超时失败。
setMaxConcurrentCheckpoints 表示同时有多少个 Checkpoint 在做快照,这个可以根据具体需求去做设置。 - enableExternalizedCheckpoints 表示下 Cancel 时是否需要保留当前的 Checkpoint,默认 Checkpoint 会在整个作业 Cancel 时被删除。Checkpoint 是作业级别的保存点。
上面讲过,除了故障恢复之外,还需要可以手动去调整并发重新分配这些状态。手动调整并发,必须要重启作业并会提示 Checkpoint 已经不存在,那么作业如何恢复数据?一方面 Flink 在 Cancel 时允许在外部介质保留 Checkpoint ;另一方面,Flink 还有另外一个机制是 SavePoint。
SavePoint
Savepoint 与 Checkpoint 类似,同样是把状态存储到外部介质。当作业失败时,可以从外部恢复。Savepoint 与 Checkpoint 有什么区别呢?
- 从触发管理方式来讲,Checkpoint 由 Flink 自动触发并管理,而 Savepoint 由用户手动触发并人肉管理;
- 从用途来讲,Checkpoint 在 Task 发生异常时快速恢复,例如网络抖动或超时异常,而 Savepoint 有计划地进行备份,使作业能停止后再恢复,例如修改代码、调整并发;
- 最后从特点来讲,Checkpoint 比较轻量级,作业出现问题会自动从故障中恢复,在作业停止后默认清除;而 Savepoint 比较持久,以标准格式存储,允许代码或配置发生改变,恢复需要启动作业手动指定一个路径恢复。
可选的状态存储方式
Checkpoint 的存储,第一种是内存存储,即 MemoryStateBackend,构造方法是设置最大的 StateSize,选择是否做异步快照,这种存储状态本身存储在 TaskManager 节点也就是执行节点内存中的,因为内存有容量限制,所以单个 State maxStateSize 默认 5 M,且需要注意 maxStateSize <= akka.framesize 默认 10 M。Checkpoint 存储在 JobManager 内存中,因此总大小不超过 JobManager 的内存。推荐使用的场景为:本地测试、几乎无状态的作业,比如 ETL、JobManager 不容易挂,或挂掉影响不大的情况。不推荐在生产场景使用。
另一种就是在文件系统上的 FsStateBackend ,构建方法是需要传一个文件路径和是否异步快照。State 依然在 TaskManager 内存中,但不会像 MemoryStateBackend 有 5 M 的设置上限,Checkpoint 存储在外部文件系统(本地或 HDFS),打破了总大小 Jobmanager 内存的限制。容量限制上,单 TaskManager 上 State 总量不超过它的内存,总大小不超过配置的文件系统容量。推荐使用的场景、常规使用状态的作业、例如分钟级窗口聚合或 join、需要开启 HA 的作业。
还有一种存储为 RocksDBStateBackend ,RocksDB 是一个 key/value 的内存存储系统,和其他的 key/value 一样,先将状态放到内存中,如果内存快满时,则写入到磁盘中,但需要注意 RocksDB 不支持同步的 Checkpoint,构造方法中没有同步快照这个选项。不过 RocksDB 支持增量的 Checkpoint,也是目前唯一增量 Checkpoint 的 Backend,意味着每次用户不需要将所有状态都写进去,将增量的改变的状态写进去即可。它的 Checkpoint 存储在外部文件系统(本地或 HDFS),其容量限制只要单个 TaskManager 上 State 总量不超过它的内存 + 磁盘,单 Key 最大 2G,总大小不超过配置的文件系统容量即可。推荐使用的场景为:超大状态的作业,例如天级窗口聚合、需要开启 HA 的作业、最好是对状态读写性能要求不高的作业。
配置State Backend
1.修改flink-conf.yaml(对应全局任务)
在配置文件里通过指定state.backend的值来选择使用哪种state backend,可配的值:
-
jobmanager 表示使用MemoryStateBackend
-
filesystem 表示使用FsStateBackend
-
rocksdb 表示使用RocksDBStateBackend
另外还有个state.checkpoints.dir,用来指定所有的backends在写checkpoint数据和元数据文件的路径
比如在配置文件里可以这样:
# The backend that will be used to store operator state checkpoints
state.backend: filesystem
# Directory for storing checkpoints
state.checkpoints.dir: hdfs://namenode:40010/flink/checkpoints
2.在代码中指定(对应单个job)
比如指定使用FsStateBackend
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setStateBackend(new FsStateBackend("hdfs://namenode:40010/flink/checkpoints"));
如果要使用RocksDBStateBackend,需要在添加以下依赖:
<dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-statebackend-rocksdb_2.11</artifactId> <version>1.10.0</version> <scope>provided</scope> </dependency>
在使用RocksDBStateBackend时候,建议使用增量checkpoints,配置方式:
- 在flink-conf.yaml中配置state.backend.incremental: true
- 在代码中
//使用RocksDBStateBackend StateBackend rockdbBackend = new RocksDBStateBackend("hdfs://namenode:40010/flink/checkpoints", true); env.setStateBackend(rockdbBackend);
参考:Flink 官网