Flink在kafka中Exactly-Once原理解说

Apache Flink中的端到端完全一次处理概述(与Apache Kafka一样!)
2017年12月发布的Apache Flink 1.4.0为Flink引入了一个重要的流程处理里程碑:一个名为TwoPhaseCommitSinkFunction(相关的Jira here)的新功能,它提取了两阶段提交协议的通用逻辑,并使得构建结束使用Flink和一系列数据源和接收器(包括Apache Kafka版本0.11及更高版本)完成一次应用程序。它提供了一个抽象层,并要求用户只实现少数几个方法来实现端到端的一次性语义。

如果您需要了解所有内容,请告诉我们Flink文档中的相关位置,您可以在其中阅读有关如何投入TwoPhaseCommitSinkFunction使用的信息。

但是如果你想了解更多信息,我们将在这篇文章中分享对新功能的深入概述以及Flink幕后发生的事情。

在本文的其余部分,我们将:

描述Flink检查点在Flink应用程序中保证一次性结果的作用。
显示Flink如何通过两阶段提交协议与数据源和数据接收器交互,以提供端到端的一次性保证。
详细介绍如何使用TwoPhaseCommitSinkFunction实现一次性文件接收器的简单示例。
Apache Flink应用程序中的完全一次语义
当我们说“确切一次语义”时,我们的意思是每个传入事件只影响最终结果一次。即使机器或软件出现故障,也没有重复数据,也没有未经处理的数据。

Flink长期以来在 Flink应用程序中提供了一次性语义。在过去几年中,我们已经深入探讨了Flink的检查点,这是Flink提供精确一次语义的能力的核心。Flink文档还提供了该功能的全面概述。

在继续之前,这里是检查点算法的快速摘要,因为理解检查点对于理解这个更广泛的主题是必要的。

Flink中的检查点是以下内容的一致快照:

应用程序的当前状态
输入流中的位置
Flink以固定的可配置间隔生成检查点,然后将检查点写入持久存储系统,例如S3或HDFS。将检查点数据写入持久存储是异步发生的,这意味着Flink应用程序在检查点过程中继续处理数据。

如果发生机器或软件故障,重新启动后,Flink应用程序将从最近成功完成的检查点恢复处理; Flink恢复应用程序状态,并在再次开始处理之前从检查点回滚到输入流中的正确位置。这意味着Flink计算结果,就好像从未发生过失败一样。

在Flink 1.4.0之前,一次性语义仅限于Flink应用程序的范围,并且没有扩展到Flink在处理后发送数据的大多数外部系统。

但是Flink应用程序与各种数据接收器一起运行,并且开发人员应该能够在一个组件的上下文之外保持一次性语义。

提供端到端的一次性语义 - 即除了Flink应用程序的状态之外,还应用于Flink写入的外部系统的语义 - 这些外部系统必须提供提交或回滚写入的方法与Flink的检查点协调。

协调分布式系统中的提交和回滚的一种常用方法是两阶段提交协议。在下一节中,我们将进入幕后讨论Flink如何TwoPhaseCommitSinkFunction 利用两阶段提交协议来提供端到端的一次性语义。

使用Apache Flink完全一次端到端应用程序
我们将介绍两阶段提交协议以及它如何在一个读取和写入Kafka的示例Flink应用程序中实现端到端的一次性语义。Kafka是一个与Flink一起使用的流行消息传递系统,Kafka最近通过其0.11版本添加了对事务的支持。这意味着Flink现在拥有必要的机制,可以在从Kafka接收数据和向Kafka写入数据时在应用程序中提供端到端的一次性语义。

Flink对端到端一次性语义的支持不仅限于Kafka,您可以将它与任何提供必要协调机制的源/接收器一起使用。例如,来自Dell / EMC的开源流媒体存储系统Pravega也支持Flink通过端口进行端到端的一次性语义TwoPhaseCommitSinkFunction。
在这里插入图片描述
在我们今天要讨论的示例Flink应用程序中,我们有:

从Kafka读取的数据源(在Flink,KafkaConsumer)
窗口聚合
将数据写回Kafka的数据接收器(在Flink,KafkaProducer中)
要使数据接收器提供一次性保证,它必须在事务范围内将所有数据写入Kafka。提交捆绑了两个检查点之间的所有写入。

这可确保在发生故障时回滚写入。

但是,在具有多个并发运行的接收器任务的分布式系统中,简单的提交或回滚是不够的,因为所有组件必须在提交或回滚时“一致”以确保一致的结果。Flink使用两阶段提交协议及其预提交阶段来解决这一挑战。

检查点的启动表示我们的两阶段提交协议的“预提交”阶段。当检查点启动时,Flink JobManager会将检查点屏障(将数据流中的记录分为进入当前检查点的集合与进入下一个检查点的集合)注入数据流。

屏障从操作员传递给操作员。对于每个运算符,它会触发运算符的状态后端以获取其状态的快照。
在这里插入图片描述
数据源存储其Kafka偏移量,在完成此操作后,它将检查点屏障传递给下一个操作员。

如果操作员仅具有内部状态,则此方法有效。内部状态是由Flink的状态后端存储和管理的所有内容 - 例如,第二个运算符中的窗口总和。当进程只有内部状态时,除了在检查点之前更新状态后端中的数据之外,不需要在预提交期间执行任何其他操作。Flink负责在检查点成功的情况下正确提交这些写入,或者在发生故障时中止它们。

在这里插入图片描述
但是,当进程具有外部状态时,必须稍微处理此状态。外部状态通常以写入外部系统(如Kafka)的形式出现。在这种情况下,为了提供一次性保证,外部系统必须为与两阶段提交协议集成的事务提供支持。

我们知道我们示例中的数据接收器具有这样的外部状态,因为它正在向Kafka写入数据。在这种情况下,在预提交阶段,除了将其状态写入状态后端之外,数据接收器还必须预先提交其外部事务。在这里插入图片描述
当检查点屏障通过所有操作员并且触发的快照回调完成时,预提交阶段结束。此时检查点已成功完成,并且包含整个应用程序的状态,包括预先提交的外部状态。如果发生故障,我们将从此检查点重新初始化应用程序。

下一步是通知所有操作员检查点已成功。这是两阶段提交协议的提交阶段,JobManager为应用程序中的每个操作员发出检查点完成的回调。数据源和窗口运算符没有外部状态,因此在提交阶段,这些运算符不必执行任何操作。但是,数据接收器确实具有外部状态,并使用外部写入提交事务。
在这里插入图片描述
所以让我们把所有这些不同的部分组合在一起:

一旦所有操作员完成预提交,他们就会发出提交。
如果至少一个预提交失败,则所有其他提交都将中止,然后我们回滚到上一个成功完成的检查点。
在成功预先提交之后,必须保证提交最终成功 - 我们的运营商和我们的外部系统都需要做出这种保证。如果提交失败(例如,由于间歇性网络问题),整个Flink应用程序将失败,根据用户的重新启动策略重新启动,并且还有另一次提交尝试。此过程至关重要,因为如果提交最终未成功,则会发生数据丢失。
因此,我们可以确定所有运营商都同意检查点的最终结果:所有运营商都同意数据已提交或提交被中止并回滚。

在Flink中实现两阶段提交运算符
将两阶段提交协议放在一起所需的所有逻辑可能有点复杂,这就是为什么Flink将两阶段提交协议的通用逻辑提取到抽象TwoPhaseCommitSinkFunction类中的原因.

我们将讨论如何扩展TwoPhaseCommitSinkFunction基于文件的简单示例。我们只需要实现四种方法,并为完全一次的文件接收器提供它们的实现:

beginTransaction - 为了开始事务,我们在目标文件系统的临时目录中创建一个临时文件。随后,我们可以在处理数据时将数据写入此文件。
preCommit - 在预提交时,我们刷新文件,关闭它,再也不要写入它。我们还将为属于下一个检查点的任何后续写入启动新事务。
commit - 在提交时,我们将预先提交的文件原子地移动到实际的目标目录。请注意,这会增加输出数据可见性的延迟。
abort - 在中止时,我们删除临时文件。
我们知道,如果发生任何故障,Flink会将应用程序的状态恢复到最新的成功检查点。一个潜在的问题是在极少数情况下,在成功预先提交之后但在通知该事实(提交)到达我们的运营商之前发生故障。在这种情况下,Flink将我们的运营商恢复到已经预先提交但尚未提交的状态。

我们必须在检查点状态下保存有关预提交事务的足够信息,以便能够重启abort或commit重启后的事务。在我们的示例中,这将是临时文件和目标目录的路径。

在TwoPhaseCommitSinkFunction采用此方案考虑在内,它总是发出从检查点恢复状态时先发制人的承诺。我们有责任以幂等的方式实现提交。一般来说,这应该不是问题。在我们的示例中,我们可以识别出这样的情况:临时文件不在临时目录中,但已经移动到目标目录。

还有一些其他边缘情况TwoPhaseCommitSinkFunction也会考虑在内。在Flink文档中了解更多信息。

包起来
如果您已经做到这一点,感谢您通过详细的帖子与我们在一起。以下是我们涉及的一些要点:

Flink的检查点系统作为Flink的基础,支持两阶段提交协议并提供端到端的一次性语义。
这种方法的一个优点是Flink不像其他一些系统那样实现传输中的数据 - 不需要像大多数批处理那样将- 计算的每个阶段写入磁盘。
Flink的新功能TwoPhaseCommitSinkFunction提取了两阶段提交协议的通用逻辑,并使用Flink和支持- 事务的外部系统构建端到端的一次性应用程序成为可能
从Flink 1.4.0开始,Pravega和Kafka 0.11生成器都提供了一次性语义; Kafka首次在Kafka 0.11中引入了交易,这使得Kafka在Flink中成为可能的生产商。
在卡夫卡0.11制片人是在顶部实现TwoPhaseCommitSinkFunction,它比一次在-至少卡夫卡生产者提供了非常低的开销。
我们对这个新功能的实现感到非常兴奋,我们期待能够TwoPhaseCommitSinkFunction在未来为其他生产商提供支持。

发布了9 篇原创文章 · 获赞 2 · 访问量 806

猜你喜欢

转载自blog.csdn.net/m0_37611613/article/details/104316013