一、State
想要回答这个问题,先来看看一段描述:
假设我们以Kafka作为数据源,利用Flink做word count计算。考虑下面几个问题:
- 为了精准地计算最终各个单词的出现次数,当由于某种原因导致故障时怎么确保恢复后已计算过的单词不会再次被计入次数?
- 单词的计数过程是在已计算得到的中间结果的基础上,将正在处理的单词对应的个数加一,那么中间结果保存在哪里?
针对第一个问题,可以将Kafka Topic的各个Partition目前消费到的offset保存起来,这样当产生再次从故障中恢复时,我们可以接着从相应的offset继续消费。offset保存在哪里呢?这个问题和第二个问题一样,答案就是,Flink会利用State存储这些任务处理过程中需要的数据(元数据属性和中间计算结果),State相当于这些数据在某个时刻的快照,这些State会被保存到配置的StateBackend中。
二、StateBackend
Flink中有三种可用的状态后端:
2.1、MemoryStateBackend
将状态维护在Java堆上的一个内部状态后端。键值状态和窗口算子使用哈希表来存储数据(values)和定时器(timers)。默认情况下,MemoryStateBackend配置成支持异步快照。异步快照可以避免阻塞数据流的处理,从而避免反压的发生。
当应用程序checkpoint时,此后端会在将状态发给JobManager之前快照下状态,JobManager也将状态存储在Java堆上。
注意:
-
默认情况下,每一个状态的大小限制为5MB。可以通过MemoryStateBackend的构造函数增加这个大小。
-
状态大小受到akka帧大小的限制,所以无论怎么调整状态大小配置,都不能大于akka的帧大小。也可以通过akka.framesize调整akka帧大小。
-
状态的总大小不能超过JobManager的内存。
2.2、FsStateBackend
使用文件系统进行状态维护,所以需要配置URL(类型、地址、路径)。比如:
-
hdfs://namenode:9002/flink/checkpoints
-
s3://flink/checkpoint
默认情况下,FsStateBackend配置成提供异步快照,以避免在状态checkpoint时阻塞数据流的处理。
当选择使用FsStateBackend时,正在进行的数据会被存在TaskManager的内存中。在checkpoint时,此后端会将状态快照写入配置的文件系统和目录的文件中,同时会在JobManager的内存中存储极少的元数据,在高可用场景下可以将元数据存储在Zookeeper中。
注意:
-
当前的状态仍然会先存在TaskManager中,所以状态的大小不能超过TaskManager的内存。
2.3、RocksDBStateBackend
RocksDBStateBackend使用时也需要配置一个URL(类型、地址、路径):
-
hdfs://namenode:9002/flink/checkpoints
-
s3://flink/checkpoint
默认情况下,RocksDBStateBackend也是配置成提供异步快照,以避免在状态checkpoint时阻塞数据流的处理。
RocksDB是一种嵌入式的本地数据库,将处理中的数据使用RocksDB存储在本地磁盘上。在checkpoint时,整个RocksDB数据库会被存储到配置的文件系统中,或者在超大状态作业时可以将增量的数据存储到配置的文件系统中,同时会在JobManager中存储极少的元数据,在高可用场景下可以将元数据存储在Zookeeper中。
注意:
-
RocksDB支持的单key和单value的大小最大为2^31字节。这是因为RocksDB的JNI API是基于byte[]的。
-
我们需要强调的是,对于使用具有合并操作的状态的应用程序,例如ListState,随着时间可能会累积到超过231字节大小,这将会导致在接下来的查询中失败。
-
使用RocksDB时,状态大小只受限于磁盘可用空间的大小,这也使得RocksDBStateBackend成为管理超大状态的最佳选择。使用RocksDB的权衡点在于所有的状态相关的操作都需要序列化(或反序列化)才能跨越JNI边界。与上面提到的堆上后端(Heap)相比,这可能会影响应用程序的吞吐量。
参考自:如何选择状态后端