流式计算分为无状态和有状态两种情况;无状态的计算观察每个独立事件,并根据最后一个事件输出结果;有状态的计算则会基于多个事件输出结果
Flink状态管理包含哪些?
状态一致性,故障处理以及高效存储和访问,以便开发人员可以专注于应用程序的逻辑
1、状态的类型
在Flink中,状态始终与特定算子相关联,总的来说,有两种类型的状态:
- 算子状态(Operator State)
- 键控状态(Keyed State)
1.1 算子状态(Operator State)
算子状态的作用范围限定为算子任务,由同一并行任务所处理的所有数据都可以访问到相同的状态,状态对于同一任务而言是共享的,算子状态不能由相同或不同算子的另一个任务访问。
Flink为算子状态提供三种基本数据结构:
-
列表状态(List state)
将状态表示为一组数据的列表
-
联合列表状态(Union list state)
也将状态表示为数据的列表;它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复
-
广播状态(Broadcast state)
如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态
1.2 键控状态(Keyed State)
键控状态是根据输入数据流中定义的键(key)来维护和访问的;Flink为每个key维护一一个状态实例,并将具有相同键的所有数据,都分区到同一个算子任务中,这个任务会维护和处理这个key对应的状态;当任务处理一条数据时, 它会自动将状态的访问范围限定为当前数据的key;因此,具有相同key的所有数据都会访问相同的状态。
Flink的Keyed State支持以下数据类型:
-
值状态(Value state)
将状态表示为单个的值
-
列表状态(List state)
将状态表示为一组数据的列表
-
映射状态(Map state)
将状态表示为一-组Key-Value对
-
聚合状态(Reducing state & Aggregating State)
将状态表示为一个用于聚合操作的列表
2、 状态一致性
一致性实际上是“正确性级别”的另一种说法
有状态的流处理,内部每个算子任务都可以有自己的状态
对于流处理器内部来说,所谓的状态一致性, 其实就是我们所说的计算结果要保证准确
一条数据不应该丢失,也不应该重复计算
在遇到故障时可以恢复状态,恢复以后的重新计算,结果应该也是完全正确的
2.1 一致性级别
-
at-most-once
这其实是没有正确性保障的委婉说法,故障发生之后,计数结果可能丢失
-
at-least-once
这表示计数结果可能大于正确值,但绝对不会小于正确值;也就是说,计数程序在发生故障后可能多算,但是绝不会少算
-
exactly-once
这指的是系统保证在发生故障后得到的计数结果与正确值一致
Flink的一个重大价值在于,它既保证了exactly-once,也具有低延迟和高吞吐的处理能力
2.2 端到端(end-to-end)状态一致性
目前我们看到的一致性保证都是由流处理器实现的,也就是说都是在 Flink 流处理器内部保证的;而在真实应用中,流处理应用除了流处理器以外还包含了数据源(例如Kafka)和输出到持久化系统
端到端的一致性保证,意味着结果的正确性贯穿了整个流处理应用的始终;每一个组件都保证了它自己的一致性,整个端到端的一致性级别取决于所有组件中一致性最弱的组件
端到端(end-to-end)状态一致性具体可以划分如下:
-
内部保证:依赖checkpoint
-
source端:需要外部源可重设数据的读取位置
-
sink端:需要保证从故障恢复时,数据不会重复写入外部系统
sink端有哪两种具体的实现方式?
-
幂等写入
所谓幂等操作,是说一个操作,可以重复执行很多次,但只导致一次结果更改,也就是说,后面再重复执行就不起作用了
-
事务写入
需要构建事务来写入外部系统,构建的事务对应着checkpoint,等到checkpoint真正完成的时候,才把所有对应的结果写入sink系统中
事务写入有两种实现方式:
- 预写日志(WAL)
- 两阶段提交(2PC)
-
不同source和sink的一致性保证可以用下表说明:
sink\source | 不可重置 | 可重置 |
---|---|---|
任意(Any) | at-most-once | at-least-once |
幂等 | at-most-once | exactly-once(故障恢复时会出现暂时不一致) |
预写日志(WAL) | at-most-once | at-least-once |
两阶段提交(2PC) | at-most-once | exactly-once |
3、检查点(checkpoint)
Flink具体如何保证exactly-once呢?它使用一种被称为“检查点”(checkpoint)的特性,在出现故障时将系统重置回正确状态
Flink检查点的核心作用时确保状态正确,集市遇到程序中断,也要正确
检查点分割线(checkpoint barriers)和普通数据记录类似,它们由算子处理,但并参与计算,而是会触发与检查点相关的行为
检查点是Flink最有价值的创新之一,因为它使Flink可以保证exactly-once,并且不需要牺牲性能
3.1 如何从检查点恢复状态?
在执行流应用程序期间,Flink会定期保存状态一致的检查点,如果发生故障,Flink将会使用最近的检查点来一致恢复应用程序的状态,并重新启动处理流程,遇到故障之后
第一步:重启应用
第二步:从checkpoint中读取状态,将状态重置,从检查点重新启动应用程序后,其内部状态与检查点完成时的状态完全相同
第三步:开始消费并处理检查点到发生故障之间的所有数据,这种检查点的保存和恢复机制可以为应用程序状态提供“精确一次”(exactly-once)的一致性,因为所有算子都会保存检查点并恢复其所有状态,这样一来所有的输入流就都会被重置到检查点完成时的位置
3.2 保存点(savepoint)
Flink还提供了可以自定义的镜像保存功能,就是保存点(savepoint)
原则上,创建保存点使用的算法与检查点完全相同,因此保存点可以认为就是具有一些额外元数据的检查点
Flink不会自动创建保存点,因此用户(或者外部调度程序)必须明确地触发创建操作
保存点是一一个强大的功能,除了故障恢复外,保存点可以用于:有计划的手动备份,更新应用程序,版本迁移,暂停和重启应用等等
3.3 检查点(checkpoint)和保存点(savepoint)的区别?
-
checkpoint的侧重点是容错,即Flink作业意外失败并重启之后,能够直接从早先打下的checkpoint恢复运行,且不影响作业逻辑的准确性
savepoint的侧重点是维护,即Flink作业需要在人工干预下手动重启、升级、迁移或A/B测试时,先将状态整体写入可靠存储,维护完毕之后再从savepoint恢复现场
-
savepoint是通过checkpoint机制创建的,所以savepoint本质上是特殊的checkpoint
-
checkpoint面向Flink Runtime本身,由Flink的各个TaskManager定时触发快照并自动清理,一般不需要用户干预
savepoint面向用户,完全根据用户的需要触发与清理。
-
checkpoint的频率往往比较高(因为需要尽可能保证作业恢复的准确度),所以checkpoint的存储格式非常轻量级,但作为trade-off牺牲了一切可移植(portable)的东西,比如不保证改变并行度和升级的兼容性
savepoint则以二进制形式存储所有状态数据和元数据,执行起来比较慢而且贵,但是能够保证portability,如并行度改变或代码升级之后,仍然能正常恢复
-
checkpoint是支持增量的(通过RocksDB),特别是对于超大状态的作业而言可以降低写入成本
savepoint并不会连续自动触发,所以savepoint没有必要支持增量。
4、Flink + Kafka如何实现端到端的exactly-once语义
我们知道,端到端的状态一致性的实现,需要每一个组件都实现,对于 Flink + Kafka 的数据管道系统(Kafka 进、Kafka 出)而言,各组件怎样保证exactly-once语义呢?
- 内部:利用checkpoint机制,把状态存盘,发生故障的时候可以恢复,保证内部的状态一致性
- source:kafka consumer作为source,可以将偏移量保存下来,如果后续任务出现了故障,恢复的时候可以由连接器重置偏移量,重新消费数据,保证一致性
- sink:kafka producer作为sink,采用两阶段提交sink,需要实现一个TwoPhaseCommitSinkFunction
kafka精确一致性(Exactly-once ) 两阶段提交的步骤?
- 第一条数据来了之后,开启一个kafka的事务(transaction),正常写入kafka分区日志但标记为未提交,这就是“预提交”
- JobManager触发checkpoint操作,barrier从source开始向下传递,遇到barrier的算子将状态存入状态后端,并通知JobManager
- sink连接器收到barrier,保存当前状态,存入checkpoint,通知JobManager,并开启下一阶段的事务,用于提交下个检查点的数据
- JobManager收到所有任务的通知,发出确认信息,表示checkpoint完成
- sink任务收到JobManager的确认信息,正式提交这段时间的数据
- 外部kafka关闭事务,提交的数据可以正常消费了
5、状态后端(state backend)
5.1 什么是状态后端?
每传入一条数据,有状态的算子任务都会读取和更新状态
由于有效的状态访问对于处理数据的低延迟至关重要,因此每个并行任务都会在本地维护其状态,以确保快速的状态访问
状态的存储、访问以及维护,由一个可插入的组件决定,这个组件就叫做状态后端(state backend)
5.2 后端的职责
-
本地的状态管理
-
将检查点(checkpoint)状态写入远程存储
5.3 状态后端的类型
-
MemoryStateBackend
- 内存级的状态后端,会将键控状态作为内存中的对象进行管理,将它们存储在TaskManager的JVM堆上,而将checkpoint存储在JobManager的内存中
- 特点:快速、低延迟,但不稳定
-
FsStateBackend
- 将checkpoint存到远程的持久化文件系统(FileSystem)上,而对于本地状态,跟MemoryStateBackend一 样,也会存在TaskManager的JVM堆上
- 同时拥有内存级的本地访问速度,和更好的容错保证
-
RocksDBStateBackend
- 将所有状态序列化后,存入本地的RocksDB中存储
- 注意:RocksDB的支持并不直接包含在flink中,需要引入依赖