1 无状态和有状态计算
在讲flink state之前,必须先清楚flink无状态计算和有状态计算区别。
1.1 无状态计算
观察每个独立的事件,并且会在最后一个时间出结果。比如一些报警和监控,一直观察每个事件,当触发警报的事件来临就会触发警告。
1.2 有状态计算
有状态的计算就会基于多个事件来输出结果。
有状态计算几种应用场景:
- 去重:数据去重时候,需要了解哪些数据来过,哪些数据还没有来,已到的数据需要保存下来,保存的数据就是状态。
- 窗口计算:窗口一分钟计算一次,在窗口触发前需要把这个窗口之内的数据先保留下来,窗口到达的时候,再将整个窗口内触发的数据输出。未触发的窗口内的数据也是一种状态。
- 机器学习/深度学习:如训练的模型以及当前模型的参数也是一种状态,机器学习可能每次都用有一个数据集,需要在数据集上进行学习,对模型进行一个反馈。
- 访问历史数据:比如与昨天的数据进行对比,需要访问一些历史数据。如果每次从外部去读,对资源的消耗可能比较大,所以也希望把这些历史数据也放入状态中做对比。
有状态计算涉及中间状态的管理,下面引申出flink状态管理内容。
2 Flink状态管理
2.1 为什么需要状态管理
管理状态最直接的方式就是将数据都放到内存中,但解决内存容量限制、7*24小野高可用,备份与恢复、横向扩展、恰好计算一次等场景问题,对状态管理提出了更高的要求。
2.2 理想的状态管理
- 简单易用:提供了丰富的数据结构、多样的状态组织形式以及简洁的扩展接口,让状态管理更加易用
- 稳定高效:实时作业一般需要更低的延迟,一旦出现故障,恢复速度也需要更快;当处理能力不够时,可以横向扩展,同时在处理备份时,不影响作业本身处理性能;
- 自动可靠:Flink 提供了状态持久化,包括不丢不重的语义以及具备自动的容错能力,比如 HA,当节点挂掉后会自动拉起,不需要人工介入。
2.3 Flink状态分类
2.3.1 managed state
managed state是有Flink Runtime管理的,状态可以自动存储、自动恢复的,可以在并行度发生变化时自动重新分配状态,并且还可以更好地进行内存管理。
managed state分为两种:
- operator state:一种基于Operate的,每个操作有状态的,每个操作之间不会相互影响。例如kafka connector,就使用了operator state
- broadcast state也是operator state的一种,keyed state是operator state的特例
- 又称为non-keyed state,每一个operator state都仅与一个operator的实例绑定
- 常见的operator state是source state,例如记录当前source的offset
- keyed state:一种基于key的,它永远和key绑定,key和key之间的state没有关系,不会相互影响
- keyed state是operator state的特例,区别在于keyed state事先按照key对数据集进行了分区,每个keyed state仅对应一个Operator和Key的组合。
- keyed state可以通过Key Groups进行管理,主要用于当算子并行度发生变化时,自动重新分布
- 只能应用于 KeyedStream 的函数与操作中,例如 Keyed UDF, window statekeyed state
- 是已经分区/划分好的,每一个 key 只能属于某一个 keyed state
- operator state和keyed state比较
operator satee | keyed state | |
算子类型 | 可用于所有的operator | 只能用户于keyed stream中的operator |
状态分配 | 单个operator对应一个state | 每个key对应一个state,单个operator可能多个keys |
创建访问 | 实现CheckpointedFunction或 ListCheckpointed接口 | 重写RichFunction,通过RuntimeContext获取 |
横向扩展 | 多种状态重分配方式: 均匀分配 状态合并后再分发给每个实例 | 状态随着key自动在多个算子task上迁移 |
数据类型 | 支持的数据结构相对较少 ListState UnionListState BroadcastState | 支持的数据结构相对较多 ValueState MapState ListState ReducingState AggregatingState FoldingState |
2.3.2 raw state
Raw State 需要用户自己管理,需要自己序列化,Flink 不知道 State 中存入的数据是什么结构,只有用户自己知道,需要最终序列化为可存储的数据结构。
2.3.3 managed state和raw state比较
Managed State | Raw State | |
管理方式 | Flink Runtime托管,自动存储、恢复、伸缩 | 用户自行管理 |
数据结构 | 支持了一系列常见的数据结构 ValueState ListState MapState …… | 只支持字节数组:byte[] |
使用场景 | 大部分Flink算子 | 一般当Managed State不满足才使用 |
2.4 state有效期
任何类型的 keyed state 都可以设置有效期 (TTL),如果配置了 TTL 且状态值已过期,则会尽最大可能清除对应的值。所有状态类型都支持单元素的TTL。 这意味着列表元素和映射元素将独立到期。在使用状态 TTL 前,需要先构建一个配置StateTtlConfig对象。 然后把配置传递到 state descriptor 中启用 TTL 功能。
2.4.1 TTL更新策略
- OnCreateAndWrite:仅在创建和写入时更新(默认策略)
- OnReadAndWrite:读取和写入时时更新
2.4.2 TTL过期数据可见性
- NeverReturnExpired:过期数据不管是否被物理删除,都不返回过期数据(默认策略)
- ReturnExpiredIfNotCleanedUp:过期数据,在数据被物理删除前都会返回
2.4.3 TTL过期数据清理策略
- FULL_STATE_SCAN_SNAPSHOT :对过期状态不做主动清理,当执行完整快照(Snapshot / Checkpoint)时,会生成一个较小的状态文件,但本地状态并不会减小。唯有当作业重启并从上一个快照点恢复后,本地状态才会实际减小,因此可能仍然不能解决内存压力的问题。
- INCREMENTAL_CLEANUP:对 Heap StateBackend生效,支持增量清理
- ROCKSDB_COMPACTION_FILTER:对 RocksDB StateBackend 有效,支持增量清理
2.5 state三种存储
- MemoryStateBackend:将数据保存在java的堆里,默认异步方式快照
- 限制
- 单次状态大小最大默认被限制为5MB,这个值可以通过构造函数来更改
- 无论单次状态大小最大被限制为多少,都不可用大过akka的frame大小
- 聚合的状态都会写入JM的内存
- 场景
- 本地开发和调试
- 状态比较少的作业
- 限制
- FsStateBackend :数据在TM的内存中,当做checkpointing的时候,会将状态快照写入文件,保存在文件系统或本地目录,少量的元信息会保存在JM的内存中,默认异步方式快照
- 场景
- 状态比较大,窗口比较长,大的KV状态
- 需要做HA的场景
- 场景
- RocksDBStateBackend:保存数据在一个叫做RocksDB的数据库中,这个数据库保存在TM的数据目录中。当做checkpointing时,整个数据库会被写入文件系统和目录。少量的元信息会保存在JM的内存中,只支持异步快照。
- 限制
- 依赖于字节数组,支持的key和value的大小最大为2^31字节。对于使用Merge操作的状态,大小很可能就默默的超过了这个限制,下次获取就会失败
- 场景
- 非常大的状态,长窗口,大的KV状态
- 目前唯一支持incremental的checkpoints的策略
- 需要HA的场景
- 限制
其中 MemoryStateBackend、FsStateBackend 两种 StateBackend 在任务运行期间都会将 State 存储在内存中,两者在 Checkpoint 时将快照存储的位置不同。RocksDBStateBackend 在任务运行期间将 State 存储在本地的 RocksDB 数据库中。所以将 MemoryStateBackend、FsStateBackend 统称为 heap 模式,RocksDBStateBackend 称为 RocksDB 模式。
3 state使用总结
3.1 Heap 模式 ValueState 和 MapState 是如何存储的
Heap 模式表示所有的状态数据都存储在 TM 的堆内存中,所有的状态都存储的原始对象,不会做序列化和反序列化。
实质上 ValueState 中存 Map 与 MapState 都是一样的,存储结构都是CopyOnWriteStateMap<K, N, HashMap>,区别在于 ValueState 是用户手动创建 HashMap,MapState 是 Flink 引擎创建 HashMap。
- ValueState :相当于用户手动创建了一个 HashMap 当做 V 放到了状态引擎中。
- MapState 是:Flink 引擎帮用户创建了一个 HashMap 当做 V 放到了状态引擎中。
3.2 RocksDB 模式 ValueState 和 MapState 是如何存储的
RocksDB 模式表示所有的状态数据存储在 TM 本地的 RocksDB 数据库中。RocksDB 是一个 KV 数据库,且所有的 key 和 value 都是 byte 数组。所以无论是 ValueState 还是 MapState,存储到 RocksDB 中都必须将对象序列化成二进制当前 kv 存储在 RocksDB 中。
- ValueState :Flink 引擎会把整个Map 当做一个大 Value,存储在 RocksDB 中。对应到 RocksDB 中,100 个 KV 键值对的 Map 集合会序列化成一个 byte 数组当做 RocksDB 的 value,存储在 RocksDB 的 1 行数据中。
- 每次修改操作需要序列化反序列化整个 Map 集合,每次序列化反序列大对象会非常耗 CPU,很容易将 CPU 打满。
- MapState: 会根据 userKey,将 100 个 KV 键值对分别存储在 RocksDB 的 100 行中。
- 每次修改操作只需要序列化反序列化 userKey 那一个 KV 键值对的数据,效率较高。
3.3 ValueState和MapState对比总结
- Heap State 模式:ValueState和MapState存性能接近
- RocksDB 模式:MapState比ValueState存Map性能高,数据量越大越明显
- 建议尽量使用MapState,替换ValueState
之前有个项目,广播白名单,对实时数据进行过滤。刚开始是用MemoryStateBackend存储广播数据,结构是ValueState,数据量小没有问题。后面白名单太大了,有几百M,切换成rocksdb存储,就有问题了。ValueState换成MapState,性能降下来了。
参考