Flink与kafka端到端完全一次语义概述

这篇文章改编自2017年Flink Forward柏林的Piotr Nowojski的演讲。您可以在Flink Forward Berlin网站上找到幻灯片和演示文稿。

2017年12月发布的Apache Flink 1.4.0为Flink引入了一个重要的流程处理里程碑:一个名为TwoPhaseCommitSinkFunction的新功能(此处为相关的Jira),它提取了两阶段提交协议的通用逻辑,并使构建端到端精确一次性的应用,该应用使用Flink以及可选的数据源和接收器(包括Apache Kafka版本0.11及更高版本)。它提供了一个抽象层,并要求用户只实现少数几个方法来实现端到端的一次性语义。

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

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

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

  • 描述Flink检查点在Flink应用程序中保证一次性结果的作用。
  • 显示Flink如何通过两阶段提交协议与数据源和数据接收器交互,以提供端到端的一次性保证。
  • 详细介绍如何使用TwoPhaseCommitSinkFunction实现一次性文件接收器。

当我们说“确切一次语义”时,我们的意思是每个传入事件只影响最终结果一次。 即使机器或软件出现故障,也没有重复数据,也没有未经处理的数据。

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也通过TwoPhaseCommitSinkFunction与Flink支持端到端的一次性语义。

Flink与kafka端到端完全一次语义概述_第1张图片

在我们今天要讨论的示例Flink应用程序中,我们有:

  • 从Kafka读取的数据源(在Flink,KafkaConsumer)
  • 窗口聚合
  • 将数据写回Kafka的数据接收器(在Flink,KafkaProducer中)

要使数据接收器提供一次性保证,它必须在事务范围内将所有数据写入Kafka。提交捆绑了两个检查点之间的所有写入。

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

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

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

屏障从算子传递给算子。对于每个算子,它会触发算子的状态后端以获取其状态的快照。

Flink与kafka端到端完全一次语义概述_第2张图片

数据源存储其Kafka偏移量,在完成此操作后,它将检查点屏障传递给下一个算子。

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

Flink与kafka端到端完全一次语义概述_第3张图片

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

我们知道我们示例中的数据接收器具有这样的外部状态,因为它正在向Kafka写入数据。 在这种情况下,在预提交阶段,除了将其状态写入状态后端之外,数据接收器还必须预先提交其外部事务。

Flink与kafka端到端完全一次语义概述_第4张图片

当检查点屏障通过所有算子并且触发的快照回调完成时,预提交阶段结束。 此时检查点已成功完成,并且包含整个应用程序的状态,包括预先提交的外部状态。 如果发生故障,我们将从此检查点重新初始化应用程序。

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

Flink与kafka端到端完全一次语义概述_第5张图片

所以让我们把所有这些不同的部分组合在一起:

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

因此,我们可以确定所有算子都同意检查点的最终结果:所有算子都同意数据已提交或提交被中止并回滚。

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

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

  1. beginTransaction  ——为了开始事务,我们在目标文件系统的临时目录中创建一个临时文件。随后,我们可以在处理数据时将数据写入此文件。
  2. preCommit  ——在预提交时,我们刷新文件,关闭它,再也不要写入它。我们还将为属于下一个检查点的任何后续写入启动新事务。
  3. commit  ——提交,我们将预先提交的文件原子地移动到实际的目标目录。请注意,这会增加输出数据可见性的延迟。
  4. abort  ——中止,我们删除临时文件。

我们知道,如果发生任何故障,Flink会将应用程序的状态恢复到最新的成功检查点。一个潜在的问题是在极少数情况下,在成功预先提交之后但在通知该事实(提交)到达我们的算子之前发生故障。在这种情况下,Flink将我们的算子恢复到已经预先提交但尚未提交的状态。

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

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

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

总结

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

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

我们对这个新功能的实现感到非常兴奋,我们期待能够在将来使用TwoPhaseCommitSinkFunction支持其他制作人。

这篇文章首次出现在Artisans数据博客上,并由原作者Piotr Nowojski和Mike Winters贡献给Apache Flink和Flink博客。

https://flink.apache.org/features/2018/03/01/end-to-end-exactly-once-apache-flink.html

你可能感兴趣的:(Flink)