流式计算分为无状态和有状态两种情况。无状态的计算观察每个独立事件,并
根据最后一个事件输出结果。例如,流处理应用程序从传感器接收温度读数,并在
温度超过
90
度时发出警告。有状态的计算则会基于多个事件输出结果。以下是一些
例子。
⚫
所有类型的窗口。例如,计算过去一小时的平均温度,就是有状态的计算。
⚫
所有用于复杂事件处理的状态机。例如,若在一分钟内收到两个相差
20
度
以上的温度读数,则发出警告,这是有状态的计算。
⚫
流与流之间的所有关联操作,以及流与静态表或动态表之间的关联操作,
都是有状态的计算。
下图展示了无状态流处理和有状态流处理的主要区别。无状态流处理分别接收
每条数据记录
(
图中的黑条
)
,然后根据最新输入的数据生成输出数据
(
白条
)
。有状态
流处理会维护状态
(
根据每条输入记录进行更新
)
,并基于最新输入的记录和当前的
状态值生成输出记录
(
灰条
)
。
![](https://img-blog.csdnimg.cn/b11a8aa7468e4fa39357e65fe07bde93.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc29uZ19xdWFuXw==,size_15,color_FFFFFF,t_70,g_se,x_16)
上图中输入数据由黑条表示。无状态流处理每次只转换一条输入记录,并且仅
根据最新的输入记录输出结果
(
白条
)
。有状态 流处理维护所有已处理记录的状态
值,并根据每条新输入的记录更新状态,因此输出记录
(
灰条
)
反映的是综合考虑多
个事件之后的结果。
尽管无状态的计算很重要,但是流处理对有状态的计算更感兴趣。事实上,正
确地实现有状态的计算比实现无状态的计算难得多。旧的流处理系统并不支持有状
态的计算,而新一代的流处理系统则将状态及其正确性视为重中之重。
1
有状态的算子和应用程序
Flink
内置的很多算子,数据源
source
,数据存储
sink
都是有状态的,流中的数
据都是
buffer records
,会保存一定的元素或者元数据。例如
: ProcessWindowFunction
会缓存输入流的数据,
ProcessFunction
会保存设置的定时器信息等等。
在
Flink
中,状态始终与特定算子相关联。总的来说,有两种类型的状态:
⚫
算子状态(
operator state
)
⚫
键控状态(
keyed state
)
1
算子状态(
operator state
)
![](https://img-blog.csdnimg.cn/77d066548f0c41a5bb4c2720ba9e0d0d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc29uZ19xdWFuXw==,size_16,color_FFFFFF,t_70,g_se,x_16)
Flink
为算子状态提供三种基本数据结构:
⚫
列表状态(
List state
)
将状态表示为一组数据的列表。
⚫
联合列表状态(
Union list state
)
也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保
存点(
savepoint
)启动应用程序时如何恢复。
⚫
广播状态(
Broadcast state
)
如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应
用广播状态。
2
键控状态(
keyed state
)
键控状态是根据输入数据流中定义的键(
key
)来维护和访问的。
Flink
为每个键值维护
一个状态实例,并将具有相同键的所有数据,都分区到同一个算子任务中,这个任务会维护
和处理这个
key
对应的状态。当任务处理一条数据时,它会自动将状态的访问范围限定为当
前数据的
key
。因此,具有相同
key
的所有数据都会访问相同的状态。
Keyed State
很类似于
一个分布式的
key-value map
数据结构,只能用于
KeyedStream
(
keyBy
算子处理之后)。
![](https://img-blog.csdnimg.cn/ebd61fa9380845f2851b663df5d58933.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc29uZ19xdWFuXw==,size_9,color_FFFFFF,t_70,g_se,x_16)
Flink
的
Keyed State
支持以下数据类型:
⚫
ValueState<T>
保存单个的值,值的类型为
T
。
o
get
操作
: ValueState.value()
o
set
操作
: ValueState.update(T value)
⚫
ListState<T>
保存一个列表,列表里的元素的数据类型为
T
。基本操作如下:
o
ListState.add(T value)
o
ListState.addAll(List<T> values)
o
ListState.get()
返回
Iterable<T>
o
ListState.update(List<T> values)
⚫
MapState<K, V>
保存
Key-Value
对。
o
MapState.get(UK key)
o
MapState.put(UK key, UV value)
o
MapState.contains(UK key)
o
MapState.remove(UK key)
⚫
ReducingState<T>
⚫
AggregatingState<I, O>
State.clear()
是清空操作。
我们可以利用
Keyed State
,实现这样一个需求:检测传感器的温度值,如果连
续的两个温度差值超过
10
度,就输出报警。
![](https://img-blog.csdnimg.cn/1571f5b926194c53ac7d83e1101e17af.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc29uZ19xdWFuXw==,size_16,color_FFFFFF,t_70,g_se,x_16)
这里需要实现一个自定义的 RichFlatMapFuction,具体实现如下:
通过
RuntimeContext
注册
StateDescriptor
。
StateDescriptor
以状态
state
的名字
和存储的数据类型为参数。
在
open()
方法中创建
state
变量。注意复习之前的
RichFunction
相关知识。
2
状态一致性
当在分布式系统中引入状态时,自然也引入了一致性问题。一致性实际上是
"
正确性级别
"
的另一种说法,也就是说在成功处理故障并恢复之后得到的结果,与没
有发生任何故障时得到的结果相比,前者到底有多正确?举例来说,假设要对最近
一小时登录的用户计数。在系统经历故障之后,计数结果是多少?如果有偏差,是
有漏掉的计数还是重复计数?
1
一致性级别
在流处理中,一致性可以分为
3
个级别:
⚫
at-most-once:
这其实是没有正确性保障的委婉说法
——
故障发生之后,计
数结果可能丢失。同样的还有
udp
。
⚫
at-least-once:
这表示计数结果可能大于正确值,但绝不会小于正确值。也
就是说,计数程序在发生故障后可能多算,但是绝不会少算。
⚫
exactly-once:
这指的是系统保证在发生故障后得到的计数结果与正确值一
致。
曾经,
at-least-once
非常流行。第一代流处理器
(
如
Storm
和
Samza)
刚问世时只
保证
at-least-once
,原因有二。
⚫
保证
exactly-once
的系统实现起来更复杂。这在基础架构层
(
决定什么代表
正确,以及
exactly-once
的范围是什么
)
和实现层都很有挑战性。
⚫
流处理系统的早期用户愿意接受框架的局限性,并在应用层想办法弥补
(
例
如使应用程序具有幂等性,或者用批量计算层再做一遍计算
)
。
最先保证
exactly-once
的系统
(Storm Trident
和
Spark Streaming)
在性能和表现力
这两个方面付出了很大的代价。为了保证
exactly-once
,这些系统无法单独地对每条
记录运用应用逻辑,而是同时处理多条
(
一批
)
记录,保证对每一批的处理要么全部
成功,要么全部失败。这就导致在得到结果前,必须等待一批记录处理结束。因此,
用户经常不得不使用两个流处理框架
(
一个用来保证
exactly-once
,另一个用来对每
个元素做低延迟处理
)
,结果使基础设施更加复杂。曾经,用户不得不在保证
exactly-once
与获得低延迟和效率之间权衡利弊。
Flink
避免了这种权衡。
Flink
的一个重大价值在于,
它既保证了
exactly-once
,也具有低延迟和高吞吐
的处理能力
。
从根本上说,
Flink
通过使自身满足所有需求来避免权衡,它是业界的一次意义
重大的技术飞跃。尽管这在外行看来很神奇,但是一旦了解,就会恍然大悟。
(注:flink的概念相对spark更加抽象,很多概念不一样,不能用spark的思路去套flink,确实很大不同)