一、Flink中的状态
1. 基础概念
在一些分组聚合(Max/Sum等指标)、窗口运算、自定义状态处理的数据处理场景中需要保存中间结果,此中间结果即可认为是 Flink中的"状态"。
在Flink中,常会使用到算子(Operator State)、键值(Key State)、广播(Broadcast State)三种状态。
算子状态:Flink中的每个SubTask只能访问和更新本地的状态。作用域为当前算子的单个SubTask的状态即为算子状态。一个典型应用是FlinkKafkaConsumer。
键值状态:相比于算子状态,其作用域为此SubTask内的每个Key. 即SubTask在处理相同Key的数据时会共享同一个状态实例。(键值状态只适用于KeyedStream)
广播状态:一类特殊的算子状态。算子状态可以允许不同SubTask的状态值不同。广播状态则要求不同SubTask上的状态值一致。
2. 某场景下状态类型的对比理解
设想如下数据加工场景以将三种状态共存于一个作业中,方便对比着去理解三种状态:
有一个kafka日志流,其数据内容是用户的消费记录。根据一个动态变化的筛选规则筛选指定用户,并输出每个客户的消费金额。
大致处理流程如下
Source(用户日志) -> KeyBy - >Connect( Source(规则日志) ) ->Process ->Sink
假设Source/KeyBy/Process/Sink算子并行度为2,此流程中涉及到的三类状态大致如下图所示。
由上图可知,
对于SubTask Source[1] 和 SubTask Source[2],每个Subtask内的数据访问和更新的 是同一个Operator State状态实例。
对于SubTask KeyBy[1] 和 SubTask KeyBy[2],在根据客户Id进行分组后每个key相同的数据会被流转到同一个节点上。客户Id为1的数据会访问并更新 客户1的累计金额Key State实例,同样客户Id为2的数据会访问并更新 客户2 的累计金额Key State实例。即Key State 实例的作用域是SubTask内每个Key对应的数据。
对于经过connect合并了规则流后的 SubTask Process[0] & Process[1], 两个SubTask的Brocast State值是一样的(虽然存放在两个实例中)。新的筛选规则数据不断更新广播状态中的数值,每个SubTask中的数据会访问广播状态中的指定用户Id并进行筛选。
二、Flink状态的存储地址
为了提升状态访问的速度,Flink将状态本地化即将状态数据到SubTask所在机器的内存/磁盘中。这样就可以降低状态的访问和更新的耗时。
为了应对机器宕机等而导致本地状态数据丢失的问题,Flink通过checkpoint机制定时将状态数据同步到HDFS上。这样作业在恢复的时候即可从HDFS拉取到原有的状态数据。
State Type | 本地存储位置 | 做快照时的存储位置 |
Operator State | 内存 | 指定HDFS路径 |
Key State | 内存/磁盘(RocksDB)两种可选 | 指定HDFS路径 |
Brocast State | 内存 | 指定HDFS路径 |
综上所述,每个Flink状态都有自己的用途和作用范围。通过本地化存储和Checkpoint机制。Flink不仅优化了状态访问的速度,也增强了系统的容错能力。
参考: