Flink state&fault tolerant

1.状态(state)简介

1.1状态概念

在数据流中有一些算子只是一个单独的计算(map,flatmap),而有些算子需要记录跨多个事件的信息(window算子),那么称这个算子为为有状态的

参考

1.2状态分类

➢ 总的说来,有三种类型的状态

1.2.1算子状态(Operator State)

Operator State (or non-keyed state) is state that is is bound to one parallel operator instance

In a typical stateful Flink Application you don’t need operators state. It is mostly a special type of state that is used in source/sink implementations and scenarios where you don’t have a key by which state can be partitioned.

算子状态的作用范围限定为算子任务,由同一并行任务所处理的所有数据都 可以访问到相同的状态

• 状态对于同一子任务而言是共享的

• 算子状态不能由相同或不同算子的另一个子任务访问

算子状态数据结构

➢ 列表状态(List state):将状态表示为一组数据的列表

➢ 联合列表状态(Union list state):也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复

1.2.2键控状态(Keyed State)

必须是Keyed DataStream,即有进行keyBy

➢ 值状态(Value state)
     保存一个可以更新和检索的值(如上所述,每个值都对应到当前的输入数据的 key,因此算子接收到的每个 key 都可能对应一个值)。 这个值可以通过 update(T) 进行更新,通过 T value() 进行检索

➢ 列表状态(List state)

    保存一个元素的列表。可以往这个列表中追加数据,并在当前的列表上进行检索。可以通过 add(T) 或者 addAll(List<T>) 进行添加元素,通过 Iterable<T> get() 获得整个列表。还可以通过 update(List<T>) 覆盖当前的列表

➢ 映射状态(Map state)

    维护了一个映射列表。 你可以添加键值对到状态中,也可以获得反映当前所有映射的迭代器。使用 put(UK,UV) 或者 putAll(Map<UK,UV>) 添加映射。 使用 get(UK) 检索特定 key。 使用 entries()keys() 和 values() 分别检索映射、键和值的可迭代视图。你还可以通过 isEmpty() 来判断是否包含任何键值对

➢ 聚合状态(Reducing state & Aggregating State)

  • ReducingState<T> 保存一个单值,表示添加到状态的所有值的聚合。接口与 ListState 类似,但使用 add(T) 增加元素,会使用提供的 ReduceFunction 进行聚
  • AggregatingState<IN, OUT>: 保留一个单值,表示添加到状态的所有值的聚合。和 ReducingState 相反的是, 聚合类型可能与添加到状态的元素的类型不同。 接口与 ListState 类    似,但使用 add(IN) 添加的元素会用指定的 AggregateFunction 进行聚合

所有类型的状态还有一个clear() 方法,清除当前 key 下的状态数据,也就是当前输入元素的 key

具体使用  参考代码

1.2.3广播状态(Broadcast State)

如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。

Broadcast State is a special type of Operator State. It was introduced to support use cases where records of one stream need to be broadcasted to all downstream tasks, where they are used to maintain the same state among all subtasks. This state can then be accessed while processing records of a second stream.

Having the above type of use cases in mind, broadcast state differs from the rest of operator states in that:

  1. it has a map format,
  2. it is only available to specific operators that have as inputs a broadcasted stream and a non-broadcasted one, and
  3. such an operator can have multiple broadcast states with different names.

官网文档  官网文档

状态说明:

  • 由一个任务维护,并且用来计算某个结果的所有数据,都属于这个任务的状 态

  • 可以认为状态就是一个本地变量,可以被任务的业务逻辑访问

  • Flink 会进行状态管理,包括状态一致性、故障处理以及高效存储和访问,以便开发人员可以专注于应用程序的逻辑

  • 在 Flink 中,状态始终与特定算子相关联

  • 为了使运行时的 Flink 了解算子的状态,算子需要预先注册其状态

1.3状态有效期 (TTL)

任何类型的 keyed state 都可以有 有效期 (TTL)。如果配置了 TTL 且状态值已过期,则会尽最大可能清除对应的值。使用如下

val ttlConfig = StateTtlConfig
    .newBuilder(Time.seconds(1))
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
    .build
    
val stateDescriptor = new ValueStateDescriptor[String]("text state", classOf[String])
stateDescriptor.enableTimeToLive(ttlConfig)

如果您在代码中没有设置TTL,则默认为Long.MAX_VALUE    源码中如下:

// 默认为 StateTtlConfig.DISABLED
private StateTtlConfig ttlConfig = StateTtlConfig.DISABLED;

// StateTtlConfig.DISABLED设置为
public static final StateTtlConfig DISABLED =
		newBuilder(Time.milliseconds(Long.MAX_VALUE)).setUpdateType(UpdateType.Disabled).build();

强烈建议进行ttl的配置以降低集群压力,我司默认设置为1.5天(距离上次访问)

2.状态后端

Flink 管理的状态存储在 state backend 中。Flink 有两种 state backend 的实现 – 一种基于 RocksDB 内嵌 key/value 存储将其工作状态保存在磁盘上的,另一种基于堆的 state backend,将其工作状态保存在 Java 的堆内存中。这种基于堆的 state backend 有两种类型:FsStateBackend,将其状态快照持久化到分布式文件系统;MemoryStateBackend,它使用 JobManager 的堆保存状态快照。

名称Working State状态备份快照
RocksDBStateBackend本地磁盘(tmp dir)分布式文件系统全量 / 增量
  • 支持大于内存大小的状态
  • 经验法则:比基于堆的后端慢10倍
FsStateBackendJVM Heap分布式文件系统全量
  • 快速,需要大的堆内存
  • 受限制于 GC
MemoryStateBackendJVM HeapJobManager JVM Heap全量
  • 适用于小状态(本地)的测试和实验

当使用基于堆的 state backend 保存状态时,访问和更新涉及在堆上读写对象。但是对于保存在 RocksDBStateBackend 中的对象,访问和更新涉及序列化和反序列化,所以会有更大的开销。但 RocksDB 的状态量仅受本地磁盘大小的限制。还

注意,只有 RocksDBStateBackend 能够进行增量快照,这对于具有大量变化缓慢状态的应用程序来说是大有裨益的。

所有这些 state backends 都能够异步执行快照,这意味着它们可以在不妨碍正在进行的流处理的情况下执行快照

一般使用RocksDB状态后端,然后周期性的将其备份到hdfs文件上

参考文档   参考文档

3.Flink容错

3.1一致性检查点(checkpoint)

  • Flink 故障恢复机制的核心,就是应用状态的一致性检查点
  • 有状态流应用的一致检查点,其实就是所有任务的状态,在某个时间点的一份 拷贝(一份快照);这个时间点,应该是所有任务都恰好处理完一个相同的输入数据的时候

3.2从检查点恢复状态

在执行流应用程序期间,Flink 会定期保存状态的一致检查点如果发生故障, Flink 将会使用最近的检查点来一致恢复应用程序的状态,并重新启动处理流程。处理流程如下

➢ 遇到故障之后,第一步就是重启应用

➢ 第二步是从 checkpoint 中读取状态,将状态重置从检查点重新启动应用程序后,其内部状态与检查点完成时的状态完全相同

➢ 第三步:开始消费并处理检查点到发生故障之间的所有数据

这种检查点的保存和恢复机制可以为应用程序状态提供“精确一次”(exactly-once)的一致性,因为所有算子都会保存检查点并恢复其所有状 态,这样一来所有的输入流就都会被重置到检查点完成时的位置

3.3Flink 检查点算法

一种简单的想法:暂停应用,保存状态到检查点,再重新恢复应用

Flink 的改进实现:基于 Chandy-Lamport 算法的分布式快照,将检查点的保存和数据处理分离开,不暂停整个应用,算法如下:

➢ 检查点分界线(Checkpoint Barrier)

  • Flink 的检查点算法用到了一种称为分界线(barrier)的特殊数据形式,用来把一条流上数据按照不同的检查点分开

  • 分界线之前到来的数据导致的状态更改,都会被包含在当前分界线所属的检查点中;而基于分界线之后的数据导致的所有更改,就会被包含在 之后的检查点中

  • 现在是一个有两个输入流的应用程序,用并行的两个 Source 任务来读取

  • JobManager 会向每个 source 任务发送一条带有新检查点 ID 的消息,通过这 种方式来启动检查点

  • 数据源将它们的状态写入检查点,并发出一个检查点 barrier

  • 状态后端在状态存入检查点之后,会返回通知给 source 任务,source 任务就会向 JobManager 确认检查点完成

  • 分界线对齐:barrier 向下游传递,sum 任务会等待所有输入分区的 barrier 到 达

  • 对于barrier已经到达的分区,继续到达的数据会被缓存

  • 而barrier尚未到达的分区,数据会被正常处理

  • 当收到所有输入分区的 barrier 时,任务就将其状态保存到状态后端的检查点中, 然后将 barrier 继续向下游转发

  • 向下游转发检查点 barrier 后,任务继续正常的数据处理

  • Sink 任务向 JobManager 确认状态保存到 checkpoint 完毕

  • 当所有任务都确认已成功将状态保存到检查点时,检查点就真正完成了

参考文档

3.4保存点(save points)

  • Flink 还提供了可以自定义的镜像保存功能,就是保存点(savepoints) • 原则上,创建保存点使用的算法与检查点完全相同,因此保存点可以认为就是具有一些额外元数据的检查点
  • Flink不会自动创建保存点,因此用户(或者外部调度程序)必须明确地触发创建操作

  • 保存点是一个强大的功能。除了故障恢复外,保存点可以用于:有计划的手动备份,更新应用程序,版本迁移,暂停和重启应用,等等

4.状态一致性

4.1什么是状态一致性

  • 有状态的流处理,内部每个算子任务都可以有自己的状态
  • 对于流处理器内部来说,所谓的状态一致性,其实就是我们所说的计算结果要保证准确
  • 一条数据不应该丢失,也不应该重复计算
  • 在遇到故障时可以恢复状态,恢复以后的重新计算,结果应该也是完全正确的

4.2状态一致性分类

4.2.1.AT-MOST-ONCE(最多一次)

 当任务故障时,最简单的做法是什么都不干,既不恢复丢失的状态,也不重播丢失的数据。At-most-once 语义的含义是最多处理一次事件

4.2.2.AT-LEAST-ONCE(至少一次)

在大多数的真实应用场景,我们希望不丢失事件。这种类型的保障称为 at- least-once,意思是所有的事件都得处理,而一些事件还可能被处理多次

4.2.3.EXACTLY-ONCE(精确一次)

恰好处理一次是最严格的保证,也是最难实现的。恰好处理一次语义不仅仅意味着没有事件丢失,还意味着针对每一个数据,内部状态仅仅更新一次

4.3一致性检查点(Checkpoints)

  • Flink 使用了一种轻量级快照机制 —— 检查点(checkpoint)来保 证 exactly-once 语义

  • 有状态流应用的一致检查点,其实就是:所有任务的状态,在某个时 间点的一份拷贝(一份快照)。而这个时间点,应该是所有任务都恰 好处理完一个相同的输入数据的时候。

  • 应用状态的一致检查点,是 Flink 故障恢复机制的核心

4.4端到端(end-to-end)状态一致性

  • 目前我们看到的一致性保证都是由流处理器实现的,也就是说都是在 Flink 流处理器内部保证的;而在真实应用中,流处理应用除了流处 理器以外还包含了数据源(例如 Kafka)和输出到持久化系统
  • 端到端的一致性保证,意味着结果的正确性贯穿了整个流处理应用的 始终;每一个组件都保证了它自己的一致性
  • 整个端到端的一致性级别取决于所有组件中一致性最弱的组件

4.5端到端 exactly-once

4.5.1内部保证

checkpoint

4.5.2.source 端

可重设数据的读取位置

4.5.3sink 端

从故障恢复时,数据不会重复写入外部系统

  • 幂等写入
  • 事务写入

参考文档

4.6幂等写入(Idempotent Writes)

所谓幂等操作,是说一个操作,可以重复执行很多次,但只导致一次 结果更改,也就是说,后面再重复执行就不起作用了

(𝑒𝑥)(𝑛)= 𝑒𝑥

如:mysql设置主键后,对同一记录insert/update 即为幂等写入

4.7事务写入(Transactional Writes)

4.7.1事务(Transaction)概念

  • 应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消
  • 具有原子性:一个事务中的一系列的操作要么全部成功,要么一个都不做

事务需要满足ACID

4.7.2实现思想

构建的事务对应着 checkpoint,等到 checkpoint 真正完成 的时候,才把所有对应的结果写入 sink 系统中

4.7.2实现方式

➢ 预写日志

➢ 两阶段提交

4.6.1预写日志(Write-Ahead-Log,WAL)

  • 把结果数据先当成状态保存,然后在收到 checkpoint 完成的通知时, 一次性写入 sink 系统

  • 简单易于实现,由于数据在状态后端中做了缓存,所以无论什么sink 系统,都能用这种方式一批搞定

  • DataStream API 提供了一个模板类:GenericWriteAheadSink,来实现这种事务性 sink

缺点:

1.一次性写入,可能给外部系统造成压力

2.如果写入的时候外部系统挂掉了,又需要重新再写,不能保证exacty-one

4.6.2两阶段提交(Two-Phase-Commit,2PC)

  • 对于每个 checkpoint,sink 任务会启动一个事务,并将接下来所有 接收的数据添加到事务里

  • 然后将这些数据写入外部 sink 系统,但不提交它们 —— 这时只是 “预提交”

  • 当它收到 checkpoint 完成的通知时,它才正式提交事务,实现结果 的真正写入

  • 这种方式真正实现了 exactly-once,它需要一个提供事务支持的外部 sink 系统。Flink 提供了 TwoPhaseCommitSinkFunction 接口。

4.6.3 2PC 对外部 sink 系统的要求

  • 外部 sink 系统必须提供事务支持,或者 sink 任务必须能够模拟外部系 统上的事务

  • 在 checkpoint 的间隔期间里,必须能够开启一个事务并接受数据写入

  • 在收到 checkpoint 完成的通知之前,事务必须是“等待提交”的状态。在故障恢复的情况下,这可能需要一些时间。如果这个时候sink系统关闭事务(例如超时了),那么未提交的数据就会丢失

  • sink 任务必须能够在进程失败后恢复事务

  • 提交事务必须是幂等操作

不同 Source 和 Sink 的一致性保证

source/sink

不可重置

可重置

任意(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

4.7 Flink+Kafka 端到端状态一致性的保证

  • 内部 —— 利用 checkpoint 机制,把状态存盘,发生故障的时候可以恢 复,保证内部的状态一致性

  • source —— kafka cource,可以将偏移量保存下来, 如果后续任务出现了故障,恢复的时候可以由连接器重置偏移量,重新 消费数据,保证一致性

  • sink —— kafka producer 作为sink,采用两阶段提交 sink,需要实现 一个 TwoPhaseCommitSinkFunction

4.7.1Exactly-once 两阶段提交

  • JobManager 协调各个 TaskManager 进行 checkpoint 存储

  • checkpoint保存在 StateBackend中,默认StateBackend是内存级的,也可以改为文件级的进行持久化保存

  • 每个算子会对当前的状态做个快照,保存到状态后端

  • checkpoint 机制可以保证内部的状态一致性

  • 每个内部的 transform 任务遇到 barrier 时,都会把状态存到 checkpoint 里

  • sink 任务首先把数据写入外部 kafka,这些数据都属于预提交的事务;遇到barrier 时,把状态保存到状态后端,并开启新的预提交事务

  • 当所有算子任务的快照完成,也就是这次的 checkpoint 完成时,JobManager 会向所有任务发通知,确认这次 checkpoint 完成

  • sink 任务收到确认通知,正式提交之前的事务,kafka 中未确认数据改为“已确 认”

4.7.2 Exactly-once 两阶段提交步骤

  1. 第一条数据来了之后,开启一个 kafka 的事务(transaction),正常写入 kafka 分区日志但标记为未提交,这就是“预提交”

  2. jobmanager 触发 checkpoint 操作,barrier 从 source 开始向下传递,遇到 barrier 的算子将状态存入状态后端,并通知 jobmanager

  3. sink 连接器收到 barrier,保存当前状态,存入 checkpoint,通知 jobmanager, 并开启下一阶段的事务,用于提交下个检查点的数据

  4. jobmanager 收到所有任务的通知,发出确认信息,表示 checkpoint 完成

  5. sink 任务收到 jobmanager 的确认信息,正式提交这段时间的数据

  6. 外部kafka关闭事务,提交的数据可以正常消费了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值