Flink 状态管理

举报
剑指南天 发表于 2026/11/02 13:20:24 2026/11/02
【摘要】 算子任务不仅需要当前流的数据,还需要其他数据来参与计算,这里的其他数据就是状态。

1.概述

算子任务不仅需要当前流的数据,还需要其他数据来参与计算,这里的其他数据就是状态。

一个算子任务会按照并行度分为多个并行子任务执行,而不同的子任务会占据不同的任务槽(task slot)。由于不同的 slot 在计算资源上是物理隔离的,所以Flink能管理的状态在并行任务间是无法共享的,每个状态只能针对当前子任务的实例有效。按键分组之后,任务所进行的所有计算都应该只针对当前 key 有效,所以状态也应该按照 key 彼此隔离。

无论是Keyed State还是Operator State,它们都是在本地实例上维护的,也就是说每个并行子任务维护着对应的状态,算子的子任务之间状态不共享。

2. 按键分组状态(Keyed State)

状态是根据输入流中定义的键(key)来维护和访问的状态,是以 key 为作用范围进行隔离的。

2.1 值状态

状态中只保存一个值。

  • T value():获取当前状态的值;
  • update(T value):对状态进行更新,传入的参数value就是要覆写的状态值。

ValueState<Integer> lastVcState;

lastVcState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("lastVcState", Types.INT));

int lastVc = lastVcState.value() == null ? 0 : lastVcState.value();

lastVcState.update(vc);

2.2 列表状态

将需要保存的数据,以列表(List)的形式组织起来。

  • Iterable<T> get():获取当前的列表状态,返回的是一个可迭代类型Iterable<T>;
  • update(List<T> values):传入一个列表values,直接对状态进行覆盖;
  • add(T value):在状态列表中添加一个元素value;
  • addAll(List<T> values):向列表中添加多个元素,以列表values形式传入。
  • clear():清空列表状态

2.3 Map 状态

把一些键值对(key-value)作为状态整体保存起来,可以认为就是一组key-value映射的列表。

  • UV get(UK key):传入一个key作为参数,查询对应的value值;
  • put(UK key, UV value):传入一个键值对,更新key对应的value值;
  • putAll(Map<UK, UV> map):将传入的映射map中所有的键值对,全部添加到映射状态中;
  • remove(UK key):将指定key对应的键值对删除;
  • boolean contains(UK key):判断是否存在指定的key,返回一个boolean值。
  • Iterable<Map.Entry<UK, UV>> entries():获取映射状态中所有的键值对;
  • Iterable<UK> keys():获取映射状态中所有的键(key),返回一个可迭代Iterable类型;
  • Iterable<UV> values():获取映射状态中所有的值(value),返回一个可迭代Iterable类型;
  • boolean isEmpty():判断映射是否为空,返回一个boolean值。

2.4 规约状态

类似于值状态(Value),不过需要对添加进来的所有数据进行归约,将归约聚合之后的值作为状态保存下来。ReducingState<T>这个接口调用的方法类似于ListState,只不过它保存的只是一个聚合值,所以调用.add()方法时,不是在状态列表里添加元素,而是直接把新数据和之前的状态进行归约,并用得到的结果更新状态。归约逻辑的定义,是在归约状态描述器(ReducingStateDescriptor)中,通过传入一个归约函数(ReduceFunction)来实现的。

2.5 聚合状态

与归约状态非常类似,聚合状态也是一个值,用来保存添加进来的所有数据的聚合结果。与ReducingState不同的是,它的聚合逻辑是由在描述器中传入一个更加一般化的聚合函数(AggregateFunction)来定义的;这也就是之前我们讲过的AggregateFunction,里面通过一个累加器(Accumulator)来表示状态,所以聚合的状态类型可以跟添加进来的数据类型完全不同,使用更加灵活。同样地,AggregatingState接口调用方法也与ReducingState相同,调用.add()方法添加元素时,会直接使用指定的AggregateFunction进行聚合并更新状态。

2.6 状态生存时间(TTL)

在实际应用中,很多状态会随着时间的推移逐渐增长,如果不加以限制,最终就会导致存储空间的耗尽。一个优化的思路是直接在代码中调用 clear() 方法去清除状态。另一个方法就是配置一个状态的“生存时间”(time-to-live,TTL),当状态在内存中存在的时间超出这个值时,就将它清除。状态创建的时候,设置 失效时间 = 当前时间 + TTL;之后如果有对状态的访问和修改,可以再对失效时间进行更新;当设置的清除条件被触发时(比如,状态被访问的时候,或者每隔一段时间扫描一次失效状态),就可以判断状态是否失效、从而进行清除了。

StateTtlConfig ttlConfig = StateTtlConfig.newBuilder(Time.seconds(10))

                                          .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)

                                          .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired).build();

 ValueStateDescriptor<String> stateDescriptor = new ValueStateDescriptor<>("my state", String.class);

 stateDescriptor.enableTimeToLive(ttlConfig);

newBuilder() 是状态TTL配置的构造器方法,必须调用,返回一个 Builder 之后再调用 build() 方法就可以得到 StateTtlConfig 了。方法需要传入一个 Time 作为参数,这就是设定的状态生存时间。

setUpdateType() 是设置更新类型。更新类型指定了什么时候更新状态失效时间,这里的 OnCreateAndWrite 表示只有创建状态和更改状态(写操作)时更新失效时间。另一种类型 OnReadAndWrite 则表示无论读写操作都会更新失效时间,也就是只要对状态进行了访问,就表明它是活跃的,从而延长生存时间。这个配置默认为 OnCreateAndWrite。

setStateVisibility() 设置状态的可见性。所谓的“状态可见性”,是指因为清除操作并不是实时的,所以当状态过期之后还有可能继续存在,这时如果对它进行访问,能否正常读取到就是一个问题了。这里设置的 NeverReturnExpired 是默认行为,表示从不返回过期值,也就是只要过期就认为它已经被清除了,应用不能继续读取;这在处理会话或者隐私数据时比较重要。对应的另一种配置是 ReturnExpireDefNotCleanedUp,就是如果过期状态还存在,就返回它的值。

3. 算子状态(Operator State)

算子状态是一个算子并行实例上定义的状态,作用范围被限定为当前算子任务。

Flink 支持:

  • 重启时改并行度

  • 从 Savepoint 恢复时改并行度

  • 自动扩缩容(Adaptive Scheduler)

3.1 列表状态

与Keyed State中的ListState一样,将状态表示为一组数据的列表。

当算子并行度进行缩放调整时,算子的列表状态中的所有元素项会被统一收集起来,相当于把多个分区的列表合并成了一个“大列表”,然后再均匀地分配给所有并行任务。

  1. 实现 CheckpointedFunction 接口
  2. 本地变量持久化,重写 snapshotState 方法:将本地变量拷贝到算子状态中,开启 checkpoint 时才会调用
  3. 初始化本地变量,重写 initializeState 方法:程序启动和恢复时,从状态中把数据添加到本地变量,每个子任务调用一次。
state = context
        .getOperatorStateStore()
        .getListState(new ListStateDescriptor<Long>("state", Types.LONG));

3.2 联合列表状态

与 ListState 类似,联合列表状态也会将状态表示为一个列表。它与常规列表状态的区别在于,算子并行度进行缩放调整时对于状态的分配方式不同。

UnionListState 的重点就在于“联合”(union)。在并行度调整时,常规列表状态是轮询分配状态项,而联合列表状态的算子则会直接广播状态的完整列表。这样,并行度缩放之后的并行子任务就获取到了联合后完整的“大列表”,可以自行选择要使用的状态项和要丢弃的状态项。

state = context
        .getOperatorStateStore()
        .getUnionListState(new ListStateDescriptor<Long>("state", Types.LONG));

3.3 广播状态

所有分区的所有数据都会访问到同一个状态,状态就像被“广播”到所有分区一样,这种算子状态,就叫作广播状态(BroadcastState)。

1)广播流的定义和配置

DataStreamSource<String> configDS = env.socketTextStream("hadoop102", 8888);

MapStateDescriptor<String, Integer> broadcastMapState = new MapStateDescriptor<>("broadcast-state", Types.STRING, Types.INT);

BroadcastStream<String> configBS = configDS.broadcast(broadcastMapState);

BroadcastConnectedStream<WaterSensor, String> sensorBCS = sensorDS.connect(configBS);

2)调用 Process 方法,参数是 BroadcastProcessFunction 的对象。重写方法 processElement (对广播状态可读权限)和 processBroadcastElement (对广播状态有修改权限)方法。

3.4 和 Keyed State 的核心区别

维度

Keyed State

Operator State

作用域

每个 key 独立

每个并行 Subtask 独立

前提

必须 keyBy()

任意算子都能用

访问方式

RuntimeContext.getState()

实现 CheckpointedFunction

常见类型

Value/List/Map/Reduce/Aggregate

List / UnionList / Broadcast

扩缩容重分配

按 Key Group 自动搬

列表均分 / 广播全量

典型场景

按用户计数、窗口聚合

Source offset、Sink 批写缓存、广播规则

4. 状态后端

4.1 状态后端的分类

Flink中提供了两类不同的状态后端,一种是“哈希表状态后端”(HashMapStateBackend,默认),另一种是“内嵌RocksDB状态后端”(EmbeddedRocksDBStateBackend)。

4.2 状态后端的配置

4.2.1 配置默认的状态后端

在 flink-conf.yaml 中,可以使用 state.backend 来配置默认状态后端。

配置项为 hashmap,配置的就是 HashMapStateBackend;如果配置项的值是 rocksdb,这样配置的就是 EmbeddedRocksDBStateBackend。

# 默认状态后端

state.backend: hashmap

# 存放检查点的文件路径,定义了检查点和元数据写入的目录

state.checkpoints.dir: hdfs://xxxx:8020/flink/checkpoints

4.2.2 为每个作业(Per-job/Application)单独配置状态后端

env.setStateBackend(new HashMapStateBackend());

env.setStateBackend(new EmbeddedRocksDBStateBackend());

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。