Flink State 简介

什么是flink中的状态?为什么需要状态管理?
flink运行计算任务的过程中,会有很多中间处理过程。在整个任务运行的过程中,中间存在着多个临时状态,比如说某些数据正在执行一个operator,但是只处理了一半数据,另一半还没来得及处理,这也是一个状态。
假设运行过程中,由于某些原因任务挂掉了,或者flink中的多个task中的一个task挂掉了,那么它在内存中的状态都会丢失,如果这时候我们没有存储中间计算的状态,那么就意味着重启这个计算任务时,需要从头开始将原来处理过的数据重新计算一遍。如果存储了中间状态,就可以恢复到中间状态,并从该状态开始继续执行任务。这就是状态管理的意义。所以需要一种机制去保存记录执行过程中的中间状态,这种机制就是状态管理机制。
为什么需要State
与批计算相比,State是流计算特有的,批计算没有failover机制,要么成功,要么重新计算。流计算在大多数场景下是增量计算,数据逐条处理(大多数场景),每次计算是在上一次计算结果之上进行处理的,这样的机制势必要将上一次的计算结果进行存储(生产模式要持久化),另外由于机器、网络、脏数据等原因导致的程序错误,在重启job时候需要从成功的检查点进行state的恢复。增量计算、Failover这些机制都需要state的支撑。
什么场景会用到状态呢?
下面列举了常见的 4 种:
  • 去重:比如上游的系统数据可能会有重复,落到下游系统时希望把重复的数据都去掉。去重需要先了解哪些数据来过,哪些数据还没有来,也就是把所有的主键都记录下来,当一条数据到来后,能够看到在主键当中是否存在。
  • 窗口计算:比如统计每分钟 Nginx 日志 API 被访问了多少次。窗口是一分钟计算一次,在窗口触发前,如 08:00 ~ 08:01 这个窗口,前59秒的数据来了需要先放入内存,即需要把这个窗口之内的数据先保留下来,等到 8:01 时一分钟后,再将整个窗口内触发的数据输出。未触发的窗口数据也是一种状态。
  • 机器学习/深度学习:如训练的模型以及当前模型的参数也是一种状态,机器学习可能每次都用有一个数据集,需要在数据集上进行学习,对模型进行一个反馈。
  • 访问历史数据:比如与昨天的数据进行对比,需要访问一些历史数据。如果每次从外部去读,对资源的消耗可能比较大,所以也希望把这些历史数据也放入状态中做对比。

flink中状态的分类

flink中包括两种基础状态:keyed state(keyed状态)和operator state(operator状态)
比如说我们刚刚的单词计数,经过了代码“keyBy()”之后,后面的算子的状态就是keyed state,但是如果我把这句代码删除,那剩下的算子就是operate state,很简单吧,区分的条件就是 keyBy。
keyed状态
keyed 状态总是与 key 一起,且只能用在 keyedStream 中。这个状态是跟特定的key绑定的,对KeyedStream流上的每一个key,可能都对应一个state。唯一组合成(operator–key,state)的形式。Keyed State可用状态为:
  • ValueState<T>: 保存一个可以修改和获取的值(如前所述,该值的作用域为input元素的key,因此操作的每个键可能都有一个值)。修改值可以使用update(T),获取值可以使用T value()。
  • ListState<T>: 存储一个元素列表。可以追加元素,并且可以从当前存储的所有元素中获取一个可迭代(Iterable)的元素。添加元素使用add(T)或addAll(List<T>),获取元素可以使用Iterable<T> get()。还可以使用update(list <T>)修改并覆盖现有列表。
  • MapState<UK, UV>:保存了一个映射列表。可以在状态中添加键-值对,并可以从当前存储的所有map中获取一个可迭代的元素。使用put(UK, UV)或putAll(Map<UK, UV>)添加映射。获取值可以使用get(UK)。获取mappings, keys和values的可迭代数据可以分别使用entry()、keys()和values()。
  • ReducingState<T>:存储一个值,该值表示添加到该状态所有值的聚合。类似于ListState,添加元素使用add(T)通过ReduceFunction聚合。
  • AggregatingState<IN, OUT>:存储一个值,该值表示添加到该状态的所有值的聚合。与ReducingState相反,聚合类型添加到该状态的元素可以有不同类型。与ListState相同,但是使用add(IN)添加元素使用指定的AggregateFunction进行聚合。
所有类型的状态都有一个clear()方法,用于清除当前活动键(即输入元素的键)的状态。
operator状态
与Keyed State不同,Operator State跟一个特定 operator 的一个并发实例绑定,整个 operator 只对应一个state。相比较而言,在一个operator上,可能会有很多个key,从而对应多个keyed state。而且operator state可以应用于非keyed stream中。比如在一个job中,涉及到map,filter,sink等操作,那么在这些operator中,每一个可以对应着一个state(一个并行度),如果是多个并行度,那么每一个并行度都对应着一个state。对于Operator State里面没有shuffle操作的state,换句话说,就是没有keyBy这个操作。
  1. operator state是task级别的state,说白了就是每个task对应一个state
  2. Kafka Connector source中的每个分区(task)都需要记录消费的 topic 的 partition 和 offset 等信息。
  3. operator state 只有一种托管状态:ValueState
Flink中的Kafka Connector,就使用了operator state。它会在每个connector实例中,保存该实例中消费topic的所有(partition, offset)映射。

状态有效期 (TTL)

在Flink的流式计算作业中,经常会遇到一些状态数不断累积,导致状态量越来越大的情形。例如作业中定义了超长的时间窗口。对于这些情况,如果处理不好,经常导致堆内存出现 OOM,或者堆外内存(RocksDB)用量持续增长导致超出容器的配额上限,造成作业的频繁崩溃,业务不能稳定正常行。
Flink State TTL 特性,该特性可以允许对作业中定义的 Keyed 状态进行超时自动清理(通常情况下,Flink 中大多数状态都是 Keyed 状态,只有少数地方会用到 Operator 状态,因此本文的“状态”均指的是 Keyed 状态),并且提供了多个设置参数,可以灵活地设定时间戳更新的时机、过期状态的可见性等,以应对不同的需求场景。
本质上来讲,State TTL 功能给每个 Flink 的 Keyed 状态增加了一个“时间戳”,而 Flink 在状态创建、写入或读取(可选)时更新这个时间戳,并且判断状态是否过期。如果状态过期,还会根据可见性参数,来决定是否返回已过期但还未清理的状态等等。状态的清理并不是即时的,而是使用了一种 Lazy 的算法来实现,从而减少状态清理对性能的影响。
任何类型的 keyed state 都可以有 有效期 (TTL),如果配置了 TTL 且状态值已过期,则会尽最大可能清除对应的值。所有状态类型都支持单元素的TTL。 这意味着列表元素和映射元素将独立到期。
在使用状态 TTL 前,需要先构建一个配置StateTtlConfig对象。 然后把配置传递到 state descriptor 中启用 TTL 功能:
import org.apache.flink.api.common.state.StateTtlConfig;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.common.time.Time;
StateTtlConfig ttlConfig = StateTtlConfig
    .newBuilder(Time.seconds(1))
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
    .build();
//如ValueState使用    
ValueStateDescriptor<String> stateDescriptor = new ValueStateDescriptor<>("text state", String.class);
stateDescriptor.enableTimeToLive(ttlConfig);
TTL配置有以下几个选项:
  • newBuilder参数表示数据的有效期,是必选项
TTL的更新策略有两个:
  • OnCreateAndWrite:仅在创建和写入时更新(默认策略)
  • OnReadAndWrite:读取和写入时时更新
TTL的数据在过期但还未被清理时的可见性配置如下:
  • NeverReturnExpired:过期数据就像不存在一样,不管是否被物理删除,都不返回过期数据(默认策略)
  • ReturnExpiredIfNotCleanedUp:会返回过期但未清理的数据,在数据被物理删除前都会返回

State 持久化逻辑

Apache Flink版本选择用RocksDB+HDFS的方式进行State的存储,State存储分两个阶段,首先本地存储到RocksDB,然后异步地同步到远程HDFS。 这样而设计既消除了HeapStateBackend的局限(内存大小,机器坏掉丢失等),也减少了纯分布式存储的网络IO开销。
Flink状态存储
Flink 在做计算的过程中经常需要存储中间状态,来避免数据丢失和状态恢复。选择的状态存储策略不同,会影响状态持久化如何和 checkpoint 交互。Flink 提供了三种状态存储方式(状态后端):MemoryStateBackend、FsStateBackend、RocksDBStateBackend。
MemoryStateBackend:
  • 本地开发或调试。
  • 小状态场景。
FsStateBackend:
  • 大状态,长窗口或大键值状态。
  • 高可用场景。
RocksDBStateBackend:
  • 大状态,长窗口或大键值状态。
  • 高可用场景。
  • 增量checkpoint,超大状态。
posted @ 2022-12-28 14:26  司也  阅读(232)  评论(0)    收藏  举报