【Flink】如何实现端到端的一致性?

发布于:2023-01-16 ⋅ 阅读:(713) ⋅ 点赞:(0)

状态快照

        

        快照 – 是 Flink 作业状态全局一致镜像的通用术语。快照包括指向每个数据源的指针(例如,到文件或 Kafka 分区的偏移量)以及每个作业的有状态运算符的状态副本,该状态副本是处理了 sources 偏移位置之前所有的事件后而生成的状态。

        Checkpoint – 一种由 Flink 自动执行的快照,其目的是能够从故障中恢复。Checkpoints 可以是增量的,并为快速恢复进行了优化。

        外部化的 Checkpoint – 通常 checkpoints 不会被用户操纵。Flink 只保留作业运行时的最近的 n 个 checkpoints(n 可配置),并在作业取消时删除它们。但你可以将它们配置为保留,在这种情况下,你可以手动从中恢复。

        Savepoint – 用户出于某种操作目的(例如有状态的重新部署/升级/缩放操作)手动(或 API 调用)触发的快照。Savepoints 始终是完整的,并且已针对操作灵活性进行了优化。

状态快照如何工作

        Flink 使用 Chandy-Lamport algorithm 算法的一种变体,称为异步 barrier 快照(asynchronous barrier snapshotting)。

        每个checkpoint在启动的时候,flink的jobmanager会为其创建一个checkpoint coordinator(状态协调器),checkpoint coordinator全权负责本应用的快照制作。

        当 checkpoint coordinator(job manager 的一部分)指示 task manager 开始 checkpoint 时,它会让所有 sources 记录它们的偏移量,并将编号的 checkpoint barriers 插入到它们的流中。这些 barriers 流经 job graph,标注每个 checkpoint 前后的流部分。

        Checkpoint n 将包含每个 operator 的 state,这些 state 是对应的 operator 消费了严格在 checkpoint barrier n 之前的所有事件,并且不包含在此(checkpoint barrier n)后的任何事件后而生成的状态。

        当 job graph 中的每个 operator 接收到 barriers 时,它就会记录下其状态。拥有两个输入流的 Operators(例如 CoProcessFunction)会执行 barrier 对齐(barrier alignment) 以便当前快照能够包含消费两个输入流 barrier 之前(但不超过)的所有 events 而产生的状态。

 

        Flink 的 state backends 利用写时复制(copy-on-write)机制允许当异步生成旧版本的状态快照时,能够不受影响地继续流处理。只有当快照被持久保存后,这些旧版本的状态才会被当做垃圾回收。 

一致性的概念

        一致性其实就是结果的正确性。对于分布式系统而言,就是不同节点中相同数据的副本应该时时刻刻保持一致。

        对于 Flink 来说,多个节点并行处理不同的任务,要保证计算结果是正确的,就必须不漏掉任何一个数据,而且也不会重复处理同一个数据。

        流式计算本身就是一个一个来的,所以正常处理的过程中结果肯定是正确的;但在发生故障、需要恢复状态进行回滚时就需要更多的保障机制了。我们通过检查点的保存来保证状态恢复后结果的正确。

一致性的级别

最多一次(AT-MOST-ONCE)

        当任务发生故障时,直接进行重启,既不恢复丢失的状态, 也不重放丢失的数据。每个数据在正常情况下会被处理一次,遇到故障时就会丢掉,这就是 “最多处理一次”。

        如果发生故障数据可以直接被丢掉,结果的准确性就无法保证,更适用于主要的需求是快,对结果的正确性存在稍许偏差的场景。

至少一次(AT-LEAST-ONCE)

        正常的生产活动中,我们更希望保证数据不丢失,但是不能保证数据只处理一次,有些数据会被重复处理,这就是“至少一次” (at-least-once)。

        为了保证达到至少一次的状态一致性,就需要在发生故障时能够重放数据。最常见是kafka架构,这时只要记录一个偏移量,当任务发生故障重启后,重置偏移量就可以重放检查点之后的数据了。

精确一次(EXACTLY-ONCE)

        精确一次意味着所有数据不仅不会丢失,而且只被处理一次,不会重复处理。 精确一次真正意义上保证结果的绝对正确。

        要保证精确一次,首先必须能达到至少一次的要求,就是数据不丢失。所以同样需要有数据重放机制来保证这一点。另外,还需要有专门的设计保证每个数据只被处理一 次。Flink 中使用的是一种轻量级快照机制——检查点(checkpoint)来保证 exactly-once 语义。

端到端精确一次

        对于 Flink 内部来说,检查点机制可以保证故障恢复后数据不丢(在能够重放的前提下),并且只处理一次,所以已经可以做到 精确一次的一致性语义了。

        由于检查点保存的是之前所有任务处理完某个数据后的状态快照,所以重放的数据引起的状态改变一定不会包含在里面,最终结果中只处理了一次。 所以,端到端一致性的关键点,就在于输入的数据源端和输出的外部存储端。

输入端

        输入端主要指的就是 Flink 读取的外部数据源。

        想要在故障恢复后不丢数据,外部数据源就必须拥有重放数据的能力。常见的做法就是对数据进行持久化保存,并且可以重设数据的读取位置,Kafka就是一个典型的例子。在 Flink 的 Source 任务中将数据读取的偏移量保存为状态,这样就可以在故障恢复时从检查点中读取出来,对数据源重置偏移量,重新获取数据。

        数据源可重放数据,加上 Flink 的 Source 算子将偏移量作为状态保存进检查点,就可以保证数据不丢。这是达到至少一次的一致性语义的基本要求。

输出端

        精确一次最大的困难在于数据有可能重复写入外部系统。

        因为检查点保存之后,继续到来的数据也会一一处理,任务的状态也会更新,最终通过 Sink 任务将计算结果输出到外部系统;只是状态改变还没有存到下一个检查点中。这时如果出现故障,这些数据都会重新来一遍,就计算了两次。

        但对于外部系统来说,再次执行写入就会把同一个数据写入两次。 为了实现端到端精确一次,我们还需要对外部存储系统、以及 Sink 连接器有额外的要求。

        能够保证 exactly-once 一致性的写入方式有两种: 幂等写入和事务写入。

幂等写入

        幂等就是说一个操作可以重复执行很多次,但只导致一次结果更改。这并没有真正解决数据重复计算、写入的问题,而是重复写入也没关系,结果不会改变。

事务写入

        事务是指所有操作必须成功完成,否则在每个操作中所做的所有更改都会被撤消。事务有四个基本特性:原子性、 一致性、隔离性和持久性。

        在 Flink 流处理的结果写入外部系统时,构建一个事务,让写入操作可以随着检查点来提交和回滚,就可以解决重复写入的问题了。所以事务写入的基本思想就是: 用一个事务来进行数据向外部系统的写入,这个事务是与检查点绑定在一起的。当 Sink 任务 遇到 barrier 时,开始保存状态的同时就开启一个事务,接下来所有数据的写入都在这个事务中;待到当前检查点保存完毕时,将事务提交,所有写入的数据就真正可用了。如果中间过程出现故障,状态会回退到上一个检查点,而当前事务没有正常关闭,所以也会回滚,写入到外部的数据就被撤销了。

        事务写入有两种形式:预写日志(WAL)和两阶段提交(2PC)

(1)预写日志

        事务提交是需要外部存储系统支持事务的,否则没有办法真正实现写入的回撤。

        预写日志(WAL)就是一种非常简单的方式。具体步骤是:

                ①先把结果数据作为日志(log)状态保存起来

                ②进行检查点保存时,也会将这些结果数据一并做持久化存储

                ③在收到检查点完成的通知时,将所有结果一次性写入外部系统。

        优点:数据提前在状态后端中做了缓存,理论上适合所有外部存储系统,比较简单

        缺点:类似于检查点完成时做一个批处理,一次性的写入会带来一些性能上的问题,且有可能写入失败,必须等待发送成功的返回确认消息。在成功写入所有数据后,在内部再次确认相应的检 查点,这才代表着检查点的真正完成。

(2)两阶段提交

        这种提交方式是完全基于事务的,它需要外部系统提供事务支持。 具体的实现步骤为:

         ①当第一条数据到来时,或者收到检查点的分界线时,Sink 任务都会启动一个事务。

         ②接下来接收到的所有数据,都通过这个事务写入外部系统;这时由于事务没有提交,所以数据尽管写入了外部系统,但是不可用,是“预提交”的状态。

        ③当 Sink 任务收到 JobManager 发来检查点完成的通知时,正式提交事务,写入的结果就真正可用了。 当中间发生故障时,当前未提交的事务就会回滚,于是所有写入外部系统的数据也就实现了撤回。

        优点:充分利用了 Flink 现有的检查点机制,分界线的到来, 就标志着开始一个新事务;而收到来自 JobManager 的 checkpoint 成功的消息,就是提交事务的指令。

        缺点:对外部系统有很高的要求


网站公告

今日签到

点亮在社区的每一天
去签到