Flink Keyed State & Operator State

Keyed State和Operator State

Keyed State是KeyedStream上的状态。假如输入流按照id为Key进行了keyBy分组,形成一个KeyedStream,数据流中所有id为1的数据共享一个状态,可以访问和更新这个状态,以此类推,每个Key对应一个自己的状态。下图展示了Keyed State,因为一个算子子任务可以处理一到多个Key,算子子任务1处理了两种Key,两种Key分别对应自己的状态。
Flink Keyed State & Operator State_第1张图片Operator State可以用在所有算子上,每个算子子任务或者说每个算子实例共享一个状态,流入这个算子子任务的数据可以访问和更新这个状态。下图展示了Operator State,算子子任务1上的所有数据可以共享第一个Operator State,以此类推,每个算子子任务上的数据共享自己的状态。

Flink Keyed State & Operator State_第2张图片
Flink Keyed State & Operator State_第3张图片
当算子发生扩缩容时,状态会重新分配。Keyed State的按照key group重新分配,分配结果和key的分桶相关;Operator State有两种分配状态:均匀分配(Operator List State)、另一种是将所有状态合并(Union List State、Broadcast State),再分发给每个实例上。

State类型

  • Keyed State:ValueState、ListState、MapState、ReduceState、AggregateState
    ReduceState:新元素通过add(value: T)加入后,与已有的状态元素使用ReduceFunction合并为一个元素,并更新到状态里
    AggregateState:和ReduceState类似,也只有一个元素。不过AggregatingState[IN, OUT]的输入和输出类型可以不一样。
  • Operator:ListState、UnionListState、BroadcastState
    ListState:状态相关的主要逻辑有两项:一、将算子子任务本地内存数据在Checkpoint时snapshot写入存储;二、初始化或重启应用时,以一定的逻辑从存储中读出并变为算子子任务的本地内存数据。要实现CheckPointedFunction接口,在做快照和状态恢复时管理ListState状态。
  • UnionListState:与ListState类似,不过重启后的状态分配方式不同
  • BroadcastState:一般通过将某条规则流广播之后,和另一条流connect。在processBroadCastElement更新广播状态、在processElement中处理业务逻辑。通过BroadcastState可以实现流处理配置实时更新。

State TTL

Flink 中 State 支持设置 TTL:

  • MapState 的 TTL 是基于 UK 级别的
  • ValueState 的 TTL 是基于整个 key 的
  • ListState的TTL是基于每个elemnt的

Flink 中 TTL 的实现,都是将用户的 value 封装了一层,具体参考下面的 TtlValue 类:

public class TtlValue<T> implements Serializable {
 @Nullable
 private final T userValue;
 private final long lastAccessTimestamp;
}

TtlValue 类中有两个字段,封装了用户的 value 且有一个时间戳字段,这个时间戳记录了这条数据写入的时间。如果开启了 TTL,则状态中存储的 value 就是 TtlValue 对象。时间戳字段也会保存到状态引擎中,之后查询数据时,就可以通过该时间戳判断数据是否过期。

  • ValueState 将 value 封装为 TtlValue。
  • MapState 将 userValue 封装成 TtlValue。
  • ListState 将 element 封装成 TtlValue

State 中要存储哪些数据,如何存储

ValueState 会存储 key、namespace、value,缩写为 。MapState 会存储 key、namespace、userKey、userValue。

Key

ValueState 和 MapState 都是 KeyedState,也就是 keyBy 后才能使用 ValueState 和 MapState。所以 State 中肯定要保存 key。这里存储的Key就是keyBy的值

Namespace

Namespace 用于区分窗口。
假设需要统计 app1 和 app2 每个小时的 pv 指标,则需要使用小时级别的窗口。状态引擎为了区分 app1 在 7 点和 8 点的 pv 值,就必须新增一个维度用来标识窗口。
Flink 用 Namespace 来标识窗口,这样就可以在状态引擎中区分出 app1 在 7 点和 8 点的状态信息。

Value、UserKey、UserValue

ValueState 中存储具体的状态值。也就是上述例子中对应的 pv 值。MapState 类似于 Map 集合,存储的是一个个 KV 键值对。为了与 keyBy 的 key 进行区分,所以 Flink 中把 MapState 的 key、value 分别叫 UserKey、UserValue。

Heap 模式下ValueState和MapState的存储形式

Heap 模式表示所有的状态数据都存储在 TM 的堆内存中,所有的状态都存储的原始对象,不会做序列化和反序列化。(注:Checkpoint 的时候会涉及到序列化和反序列化,数据的正常读写并不会涉及,所以这里先不讨论。)
Heap 模式下,无论是 ValueState 还是 MapState 都存储在 CopyOnWriteStateMap 中。

  • key 、 Namespace 分别对应 CopyOnWriteStateMap 的 K、N。
  • ValueState 的 value 对应 CopyOnWriteStateMap 的 V。
  • MapState 将会把整个 Map 作为 CopyOnWriteStateMap 的 V,相当于 Flink 引擎创建了一个 HashMap 用于存储 MapState 的 KV 键值对。
RocksDB 模式下ValueState和MapState的存储形式

RocksDB 模式表示所有的状态数据存储在 TM 本地的 RocksDB 数据库中。RocksDB 是一个 KV 数据库,且所有的 key 和 value 都是 byte 数组。所以无论是 ValueState 还是 MapState,存储到 RocksDB 中都必须将对象序列化成二进制当前 kv 存储在 RocksDB 中。
ValueState 有 key、namespace、value 需要存储,所以最简单的思路:

  1. 将 ValueState 的 key 序列化成 byte 数组
  2. 将 ValueState 的 namespace 序列化成 byte 数组
  3. 将两个 byte 数组拼接起来做为 RocksDB 的 key
  4. 将 ValueState 的 value 序列化成 byte 数组做为 RocksDB 的 value

MapState 有 key、namespace、userKey、userValue 需要存储,所以最简单的思路:

  1. 将 MapState 的 key 序列化成 byte 数组
  2. 将 MapState 的 namespace 序列化成 byte 数组
  3. 将 MapState 的 userKey 序列化成 byte 数组
  4. 将三个 byte 数组拼接起来做为 RocksDB 的 key
  5. 将 MapState 的 value 序列化成 byte 数组做为 RocksDB 的 value
RocksDB 模式下,ValueState 中存 Map 与 MapState 有什么区别?
  • 性能
    如果使用 ValueState 中存 Map,则每次修改操作需要序列化反序列化整个 Map 集合,每次序列化反序列大对象会非常耗 CPU,很容易将 CPU 打满。
    如果使用 MapState,每次修改操作只需要序列化反序列化 userKey 那一个 KV 键值对的数据,效率较高。
    Tips:其他使用 ValueState、value 是大对象且 value 频繁更新的场景,都容易将 CPU 打满。
  • TTL
    如果 ValueState 中存 Map,则整个 Map 被当做 value,只维护一个时间戳。所以要么整个 Map 过期,要么都不过期。
    MapState 中如果存储了 100 个 KV 键值对,则 100 个 KV 键值对都会存储各自的时间戳。因此每个 KV 键值对的 TTL 是相互独立的。

Flink state实现

你可能感兴趣的:(flink)