Flink State本文目录:
- 状态是什么东西?有了状态能做什么?
- 为什么离线计算中不提状态,实时计算老是提到状态这个概念?状态到底在实时计算中解决了什么问题?
- 有了状态、为什么又出现了状态管理的概念?
- 怎么学习 Flink 中的状态、状态管理相关的概念呢?
- Flink 中状态的分类?
- Flink 中状态的使用方式?
- Flink 状态后端的分类及使用建议?
- Flink 中状态的能力扩展 - TTL?
- Flink 中状态 TTL 的原理机制?
- Flink Checkpoint 的运行机制?
- Flink Checkpoint 的配置?
- Flink Checkpoint 在 HDFS 的存储格式?
- Flink Checkpoint 的恢复机制?
- Flink SQL 的 State 存储?
- Flink 状态的误用之痛?
1. 状态是什么东西?
需要明白:状态不仅仅只限于 Flink 的状态。状态其实是一个普遍存在的东西。
2.为什么离线计算中不提状态 ,状态到底在实时计算中解决了什么问题?
其实在实时计算中的状态的功能主要体现在任务可以做到失败重启后没有数据质量、时效问题。
实时任务失败重启:实时任务一般都是 7x24 小时 long run 的,挂了之后,就会有以下两个问题。首先给一个实际场景:一个消费上游 Kafka,使用 Set去重计算 DAU 的实时任务。
- 数据质量问题:当这个实时任务挂了之后恢复,Set空了,这时候任务再继续从上次失败的 Offset 消费 Kafka 产出数据,则产出的数据就是错误数据了。这时候小伙伴可能会提出疑问,计算 DAU 场景的话,这个任务挂了我重新从今天 0 点开始消费 Kafka 不就得了?这个观点没有错,其实这就是博主即将说的第二个问题。
- 数据时效问题:你要记得,你一定要记得,你是一个实时任务,产出的指标是有时效性(主要是时延)要求的。你可以从今天 0 点开始重新消费,但是你回溯数据也是需要时间的。举例:中午 12 点挂了,实时任务重新回溯 12 个小时的数据能在 1 分钟之内完成嘛?大多数场景下是不能的!一般都要回溯几个小时,这就是实时场景中的数据时效问题。
那当我们把状态给 “管理” 起来时,上面的两个问题就可以迎刃而解。还是相同的计算任务、相同的业务场景:
当我们把 Set这个数据结构定期(每隔 1min)的给存储到 HDFS 上面时,任务挂了、恢复之后。我们的任务还可以从 HDFS 上面把这个数据给读回来,接着从最新的一个 Kafka Offset 继续计算就可以,这样即没有数据质量问题,也没有数据时效性问题。
所以这就是为什么实时任务中老是提到 状态、状态管理 这些个概念的原因!
-
那么!离线任务真的是没有状态、状态管理这些个概念这个概念嘛?
离线中其实也有,举个例子 Remote Shuffle Service,比如 Spark Remote Shuffle Service。
一个常见的离线任务运行时,通常都由几个 Stage 组成,比如有 1,2,3,4,5 个 Stage 顺序执行,当第 4 个 Stage 运行挂了之后,离线任务就要从第 1 个 Stage 重新开始执行,这样的话,执行效率是非常低的。
那么这个场景下有没有办法做到第 4 个 Stage 挂了,我们只重新运行第 4 个 Stage 呢?
当然有解法,我们可以将每一个 Stage 的结果保存下来,比如第 3 个 Stage 运行完成之后,将结果保存到远程的服务,当第 4 个 Stage 任务挂了之后,只需要从远程服务将第 3 个 Stage 结果拿来重新执行就行。
而 Remote Shuffle Service 的功能就是将每一个 Stage 的运行结果存储到一个独立的 Service 上面,当第 4 个 Stage fail 之后重新恢复时,可以直接从第 4 个 Stage 开始执行。
那么这里其实也涉及到了状态的概念。对于整个任务来说,这里面的每个 Stage 的结果就是状态,Remote Shuffle Service 就起到了 “管理” 状态 的作用。 -
那么!实时任务真的只能依赖状态、状态管理嘛?
也不一定,举个例子,一个消费 Kafka,计算一个分钟级别的同时在线用户数(TUMBLE 1 min)的实时任务,在任务挂了之后,其实可以完全不依赖状态,直接从前几分钟的 Kafka Offset 去回溯一下数据也可以,能满足时效性的同时,也可以满足数据质量。
3.有了状态、为什么又出现了状态管理的概念?
一个实时任务光有状态是没用的,我们要把这个状态 “管理” 起来,即上节案例中的把 Set定期的存储到远程 HDFS 上,离线任务将中间结果保存到 Remote Shuffle Service 上。只有这样才能在任务 failover 后将状态恢复,保障数据质量、时效。
而在 Flink 中状态管理的模块就是我们所熟知的 Flink Checkpoint\Savepoint。
4.怎么学习 Flink 中的状态、状态管理相关的概念呢?
- 状态:指 Flink 程序中的状态数据,博主认为也能代指用户在使用 DataStream API 编写程序来操作 State 的接口。你在 Flink 中见到的 ValueState、MapState 等就是指状态接口。你可以通过 MapState.put(key, value) 去往 MapState 中存储数据,MapState.get(key) 去获取数据。这也是你能直接接触、操作状态的一层。
- 状态后端:做状态数据(持久化,restore)的工具就叫做状态后端。比如你在 Flink 中见到的 RocksDB、FileSystem 的概念就是指状态后端。这些状态后端就是实际存储上面的状态数据的。比如配置了 RocksDB 作为状态后端,MapState 的数据就会存储在 RocksDB 中。再引申一下,大家也可以理解为:应用中有一份状态数据,把这份状态数据存储到 MySQL 中,这个 MySQL 就能叫做状态后端。
- Checkpoint、Savepoint:协调整个任务 when,how 去将 Flink 任务本地机器中存储在状态后端的状态去同步到远程文件存储系统(比如 HDFS)的过程就叫 Checkpoint、Savepoint。
当我们了解了这 3 个概念之后,继续往下看实际我们怎么使用 Flink 状态。
5.Flink 中状态的分类?
Flink 中的状态分类有两大类,我们可以在很多博客文章上面看到:Managed State 和 Raw State。
但是实际上生产开发中基本只会用到 Managed State,不会用到 Raw State。至少对于博主来说是这样的。所以本节我们就只介绍 Managed State。
对 Managed State 细分,它又有两种类型:operator-state 和 keyed-state。这里先对比两种状态,后续将展示具体的使用方法。
其中每一种 State 的用处:
-
ValueState[T]:是单一变量的状态,T 是某种具体的数据类型,比如 Double、String 或我们自己定义的复杂数据结构。我们可以使用 value() 方法获取状态,使用 update(value: T) 更新状态。
-
MapState[K, V]:存储一个 Key-Value map,其功能与 Java 的 Map 几乎相同。get(key: K) 可以获取某个 key 下的 value,put(key: K, value: V) 可以对某个 key 设置 value,contains(key: K) 判断某个 key 是否存在,remove(key: K) 删除某个 key 以及对应的 value,entries(): java.lang.Iterable[java.util.Map.Entry[K, V]] 返回 MapState 中所有的元素,iterator(): java.util.Iterator[java.util.Map.Entry[K, V]] 返回一个迭代器。需要注意的是,MapState 中的 key 和 Keyed State 的 key 不是同一个 key。
-
ListState[T]:存储了一个由 T 类型数据组成的列表。我们可以使用 add(value: T) 或 addAll(values: java.util.List[T]) 向状态中添加元素,使用 get(): java.lang.Iterable[T] 获取整个列表,使用 update(values: java.util.List[T]) 来更新列表,新的列表将替换旧的列表。
-
ReducingState[T]、AggregatingState[IN, OUT]、ListState[T] 同属于 MergingState[T]:与 ListState[T] 不同的是,ReducingState[T] 只有一个元素,而不是一个列表。它的原理是新元素通过 add(value: T) 加入后,与已有的状态元素使用 ReduceFunction 合并为一个元素,并更新到状态里。AggregatingState[IN, OUT] 与 ReducingState[T] 类似,也只有一个元素,只不过 AggregatingState[IN, OUT] 的输入和输出类型可以不一样。ReducingState[T] 和 AggregatingState[IN, OUT] 与窗口上进行 ReduceFunction 和 AggregateFunction 很像,都是将新元素与已有元素做聚合。
注意:
大多数情况下,常用的 State 也就是 keyed-state 中的 ValueState、MapState,其他 State 接口其实非常少用(包括 operator-state 也很少用)。
6.Flink 状态后端的分类及使用建议
[上次已经说过了]
Flink 提供了 3 种状态后端用于管理和存储状态数据,我们来看看每种状态后端的适用场景:
- MemoryStateBackend
- FSStateBackend
- RocksDBStateBackend
到生产环境中:
- 如果状态很大,使用 Rocksdb;如果状态不大,使用 Filesystem。
- Rocksdb 使用磁盘存储 State,所以会涉及到访问 State 磁盘序列化、反序列化,性能会收到影响,而 Filesystem 直接访问内存,单纯从访问状态的性能来说 Filesystem 远远好于 Rocksdb。生产环境中实测,相同任务使用 Filesystem 性能为 Rocksdb 的 n 倍,因此需要根据具体场景评估选择。
7.Flink 提供的 State 过期的 4 种删除策略:
- lazy 删除策略:就是在访问 State 的时候根据时间戳判断是否过期,如果过期则主动删除 State 数据
- full snapshot cleanup 删除策略:从状态恢复(checkpoint、savepoint)的时候采取做过期删除,但是不支持 rocksdb 增量 ck
- incremental cleanup 删除策略:访问 state 的时候,主动去遍历一些 state 数据判断是否过期,如果过期则主动删除 State 数据
- rocksdb compaction cleanup 删除策略:rockdb 做 compaction 的时候遍历进行删除。仅仅支持 rocksdb
8.Flink Checkpoint 的运行机制?
Checkpoint 整个流程如下:
- JM 定时调度 Checkpoint 的触发:JM CheckpointCoorinator 定时触发,CheckpointCoordinator 会去通过 RPC 接口调用 Source 算子的 TM 的 StreamTask 告诉 TM 可以开始执行 Checkpoint 了。
- Source 算子:接受到 JM 做 Checkpoint 的请求后,开始做本地 Checkpoint,本地执行完成之后,发 barrier 给下游算子。barrier 发送策略是随着 partition 策略走,将 barrier 发往连接到的所有下游算子(举例:keyby 就是广播,forward 就是直接送)。
- 剩余的算子:接收到上游所有 barrier 之后进行触发 Checkpoint。当一个算子接收到上游一个 channel 的 barrier 之后,就停止处理这个 input channel 来的数据(本质上就是不会再去影响状态了)
9.Flink Checkpoint 的配置
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 每 120 秒触发一次 checkpoint,不会特别频繁
env.enableCheckpointing(120000);
// Flink 框架内保证 EXACTLY_ONCE
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 两个 checkpoints 之间最少有 120s 间隔
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(120000);
// checkpoint 超时时间 600s
env.getCheckpointConfig().setCheckpointTimeout(600000);
// 同时只有一个 checkpoint 运行
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
// 取消作业时保留 checkpoint,因为有时候任务 savepoint 可能不可用,这时我们就可以直接从 checkpoint 重启任务
env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
// checkpoint 失败时 task 不失败,因为可能会有偶尔的写入 HDFS 失败,但是这并不会影响我们任务的运行
// 偶尔的由于网络抖动 checkpoint 失败可以接受,但是如果经常失败就要定位具体的问题!
env.getCheckpointConfig().setFailOnCheckpointingErrors(false);
10.Flink Checkpoint 在 HDFS 的存储格式
这里也分 keyed-state 和 operator-state 进行说明。Flink 会将 Checkpoint 数据存储在一个带有编号的 chk 目录中。
比如说一个 Flink 任务的 keyed-state 的 subTask 个数是 10,operator-state 对应的 subTask 也是 10,那么 chk 会存一个元数据文件 _metadata,10 个 keyed-state 文件,10 个 operator-state 的文件。
11.Flink 状态的误用之痛
-
一定要分清楚 operator-state 和 keyed-state 的区别以及使用方式。见过有在 KeyedStream 后面错用 operator-state,operator-state 大 State 导致 OOM。建议 KeyedStream 上还是使用 keyed-state。
-
一定要学会分场景使用 ValueState 和 MapState。有见过在 ValueState 中存储一个大 Map,并且使用 RocksDB,导致 State 访问非常慢(因为 RocksDB 访问 State 经过序列化),拖慢任务处理速度。两者的具体区别如下:
ValueState:
a. 应用场景:简单的一个变量存储,比如 Long\String 等。如果状态后端为 RocksDB,极其不建议在 ValueState 中存储一个大 Map,这种场景下序列化和反序列化的成本非常高,拖慢任务处理速度,这种常见适合使用 MapState。其实这种场景也是很多小伙伴一开始使用 State 的误用之痛,一定要避免。
b. TTL:针对整个 Value 起作用
MapState:
a. 应用场景:和 Map 使用方式一样一样的
b. TTL:针对 Map 的 key 生效,每个 key 一个 TTL
-
keyed-state 不能在 open 方法中访问、更新 state,这是不行的,因为 open 方法在执行时,还没有到正式的数据处理环节,上下文中是没有 key 的
-
operator-state 中的 ListState 进行以下操作会有问题。因为当实例化的 state 为 PartitionableListState 时,会先把 list clear,然后再 add,这样就会把下图中的 items 给 clear 了。你会发现 state 一致为空。