Beam 模型基础

Apache Beam 是一个统一模型,用于定义批处理和流式数据并行处理管道。要开始使用 Beam,您需要了解一组重要的核心概念。

以下部分更详细地介绍了这些概念,并提供了指向其他文档的链接。

管道

Beam 管道是所有数据和计算(特别是 有向无环图)在您的数据处理任务中的图。这包括读取输入数据、转换该数据和写入输出数据。管道由用户在其选择的 SDK 中构建。然后,管道通过 SDK 直接或通过运行器 API 的 RPC 接口到达运行器。例如,此图显示了一个分支管道

The pipeline applies two transforms to a single input collection. Eachtransform produces an output collection.

在此图中,框表示称为 PTransforms 的并行计算,带圆圈的箭头表示在转换之间流动的数据(以 PCollections 的形式)。数据可能是有限的、存储的、数据集,也可能是无限的數據流。在 Beam 中,大多数转换对有限数据和无限数据都适用。

您几乎可以将任何可以想到的计算表达式为图表示为 Beam 管道。Beam 驱动程序通常首先创建一个 Pipeline 对象,然后使用该对象作为创建管道的數據集和转换的基础。

有关管道的更多信息,请参阅以下页面

PCollection

PCollection 是元素的无序集合。每个 PCollection 都是一个潜在的分布式、同构数据集或数据流,并且由创建它的特定 Pipeline 对象拥有。多个管道不能共享一个 PCollection。Beam 管道处理 PCollections,运行器负责存储这些元素。

PCollection 通常包含“大数据”(太多数据无法容纳在一台机器的内存中)。有时,一小部分数据或中间结果可能适合一台机器的内存,但 Beam 的计算模式和转换侧重于需要分布式数据并行计算的情况。因此,PCollection 的元素不能单独处理,而是以并行方式统一处理。

以下 PCollection 的特性很重要。

有限与无限:

PCollection 可以是有限的,也可以是无限的。

这两个类别来自批处理和流处理的直觉,但这两个类别在 Beam 中是统一的,有限和无限的 PCollections 可以共存于同一个管道中。如果您的运行器只能支持有限的 PCollections,则必须拒绝包含无限 PCollections 的管道。如果您的运行器只针对流,那么 Beam 支持代码中有一些适配器可以将所有内容转换为针对无限数据的 API。

时间戳:

PCollection 中的每个元素都与一个时间戳相关联。

当您将一个原始连接器执行到存储系统时,该连接器负责提供初始时间戳。运行器必须传播和聚合时间戳。如果时间戳不重要,例如在某些批处理作业中,其中元素不表示事件,则时间戳将是最小的可表示时间戳,通常被称为“负无穷大”。

水印:

每个 PCollection 必须有一个 水印,它估计 PCollection 的完成程度。

水印是一个猜测,“我们永远不会看到时间戳更早的元素”。数据源负责生成水印。运行器必须在处理、合并和划分 PCollection 时实现水印传播。

当水印前进到“无穷大”时,PCollection 的内容就完整了。通过这种方式,您可以发现无界 PCollection 是有限的。

窗口化元素:

PCollection 中的每个元素都驻留在一个 窗口 中。没有元素驻留在多个窗口中;两个元素除了它们的窗口之外可能相同,但它们并不相同。

当元素写入外部世界时,它们实际上被放回全局窗口。写入数据的转换如果不考虑这种观点,可能会导致数据丢失。

窗口有一个最大时间戳。当水印超过最大时间戳加上用户指定的允许延迟时,窗口就会过期。与过期窗口相关的所有数据可能在任何时候都会被丢弃。

编码器:

每个 PCollection 都有一个编码器,它指定元素的二进制格式。

在 Beam 中,用户的管道可以用除运行器语言之外的语言编写。没有期望运行器能够实际反序列化用户数据。Beam 模型主要针对编码数据,即“字节”。每个 PCollection 都有一个为其元素声明的编码,称为编码器。编码器有一个标识编码的 URN,并且可能具有额外的子编码器。例如,列表的编码器可能包含列表元素的编码器。语言特定的序列化技术经常被使用,但也有一些常见的关键格式(如键值对和时间戳),以便运行器可以理解它们。

窗口化策略:

每个 PCollection 都有一个窗口化策略,它指定用于分组和触发操作的基本信息。Window 转换设置窗口化策略,而 GroupByKey 转换的行为受窗口化策略控制。


有关 PCollection 的更多信息,请参阅以下页面

PTransform

PTransform(或转换)表示管道中的数据处理操作或步骤。转换通常应用于一个或多个输入 PCollection 对象。读取输入的转换是一个例外;这些转换可能没有输入 PCollection

您以函数对象的形式提供转换处理逻辑(俗称“用户代码”),您的用户代码将应用于输入 PCollection(或多个 PCollection)的每个元素。根据您选择的管道运行器和后端,集群中的许多不同的工作者可能并行地执行您的用户代码的实例。在每个工作者上运行的用户代码将生成添加到零个或多个输出 PCollection 对象的输出元素。

Beam SDK 包含许多不同的转换,您可以将它们应用于管道的 PCollection。这些包括通用的核心转换,如 ParDoCombine。SDK 中还包含预先编写的复合转换,它们将一个或多个核心转换组合成有用的处理模式,例如在集合中计数或组合元素。您还可以定义自己的更复杂的复合转换,以适合管道的具体用例。

以下列表列出了一些常见的转换类型

有关转换的更多信息,请参阅以下页面

聚合

聚合是根据多个(一个或多个)输入元素计算一个值。在 Beam 中,聚合的主要计算模式是对具有共同键和窗口的所有元素进行分组,然后使用关联和交换运算对每个元素组进行组合。这类似于 MapReduce 模型中的“Reduce”操作,但它得到了增强,可以处理无界输入流以及有界数据集。

Aggregation of elements.

图 1:元素的聚合。具有相同颜色的元素表示具有共同键和窗口的元素。

一些简单的聚合转换包括 Count(计算聚合中所有元素的计数)、Max(计算聚合中的最大元素)和 Sum(计算聚合中所有元素的总和)。

当元素被分组并作为包发出时,聚合被称为 GroupByKey(关联/交换操作是包并集)。在这种情况下,输出不小于输入。通常,您将应用一个操作,例如求和,称为 CombineFn,其中输出明显小于输入。在这种情况下,聚合被称为 CombinePerKey

在实际应用中,您可能有数百万个键和/或窗口;这就是为什么这仍然是一个“令人尴尬地并行”的计算模式的原因。在您只有较少键的情况下,您可以通过添加辅助键来添加并行性,将您问题的每个自然键拆分为多个子键。在这些子键被聚合之后,结果可以进一步组合成您问题的原始自然键的结果。您聚合函数的结合性确保这会产生相同的结果,但具有更高的并行性。

当您的输入是无界时,根据键和窗口对元素进行分组的计算模式大致相同,但控制何时以及如何发出聚合结果涉及三个概念

有关可用聚合转换的更多信息,请参阅以下页面

用户定义函数 (UDF)

一些 Beam 操作允许您运行用户定义的代码作为配置转换的一种方式。例如,当使用 ParDo 时,用户定义的代码指定要对每个元素应用的操作。对于 Combine,它指定如何组合值。通过使用 跨语言转换,Beam 管道可以包含用不同语言编写的 UDF,甚至可以在同一个管道中使用多种语言。

Beam 有几种 UDF 变体

每个语言 SDK 都有自己的表达 Beam 中用户定义函数的惯用方式,但有一些共同的要求。当您为 Beam 转换构建用户代码时,您应该牢记执行的分布式特性。例如,您的函数可能在许多不同的机器上并行运行着多个副本,而这些副本独立运行,不与任何其他副本通信或共享状态。您的用户代码函数的每个副本可能会根据您为管道选择的管道运行器和处理后端,被重试或多次运行。Beam 还通过 有状态处理 API 支持有状态处理。

有关用户定义函数的更多信息,请参阅以下页面

模式

模式是 PCollection 的语言无关类型定义。PCollection 的模式将该 PCollection 的元素定义为命名字段的有序列表。每个字段都有一个名称、一个类型,可能还有一组用户选项。

在许多情况下,PCollection 中的元素类型具有可以内省的结构。一些示例包括 JSON、Protocol Buffer、Avro 和数据库行对象。所有这些格式都可以转换为 Beam 模式。即使在 SDK 管道中,简单的 Java POJO(或其他语言中的等效结构)也经常用作中间类型,这些类型也具有通过检查类可以推断出的清晰结构。通过了解管道记录的结构,我们可以为数据处理提供更简洁的 API。

Beam 提供了一组在模式上本地运行的转换。例如,Beam SQL 是一个在模式上运行的常见转换。这些转换允许根据命名字段模式进行选择和聚合。模式的另一个优点是它们允许通过名称引用元素字段。Beam 提供了一种用于引用字段的选择语法,包括嵌套字段和重复字段。

有关模式的更多信息,请参阅以下页面

运行器

Beam 运行器在特定平台上运行 Beam 管道。大多数运行器是到海量并行大数据处理系统的翻译器或适配器,例如 Apache Flink、Apache Spark、Google Cloud Dataflow 等等。例如,Flink 运行器将 Beam 管道翻译成 Flink 作业。Direct Runner 在本地运行管道,因此您可以测试、调试和验证您的管道尽可能地符合 Apache Beam 模型。

有关 Beam 运行器的最新列表以及它们支持的 Apache Beam 模型的功能,请参阅运行器 功能矩阵

有关运行器的更多信息,请参阅以下页面

Window

窗口化根据 PCollection 中各个元素的时间戳将其细分为窗口。窗口通过将集合划分为有限集合的窗口,从而使无界集合上的分组操作成为可能。

窗口函数告诉运行器如何将元素分配到一个或多个初始窗口,以及如何合并分组元素的窗口。PCollection 中的每个元素只能在一个窗口中,因此如果窗口函数为某个元素指定了多个窗口,则该元素在概念上会被复制到每个窗口中,并且每个元素除了其窗口外都是相同的。

聚合多个元素的转换,例如 GroupByKeyCombine,在每个窗口的基础上隐式地工作;它们将每个 PCollection 视为多个有限窗口的连续,尽管整个集合本身可能具有无限大小。

Beam 提供了一些窗口函数

如果您有更复杂的需求,也可以定义自己的窗口函数。

例如,假设我们有一个使用固定时间窗口的 PCollection,窗口长度为五分钟。对于每个窗口,Beam 必须收集所有事件时间时间戳在给定窗口范围内(例如,第一个窗口从 0:00 到 4:59)的数据。时间戳超出该范围的数据(来自 5:00 或更晚的数据)属于不同的窗口。

有两个概念与窗口密切相关,将在以下部分中介绍:水印触发器

有关窗口的更多信息,请参阅以下页面

水印

在任何数据处理系统中,数据事件发生的时间(“事件时间”,由数据元素本身的时间戳确定)与数据元素在管道中的任何阶段实际处理的时间(“处理时间”,由处理该元素的系统上的时钟确定)之间存在一定的延迟。此外,数据并不总是保证以时间顺序到达管道,或者总是以可预测的间隔到达。例如,您可能拥有不保留顺序的中间系统,或者您可能拥有两个服务器,它们都对数据进行时间戳,但其中一个具有更好的网络连接。

为了解决这种潜在的不可预测性,Beam 会跟踪水印。水印是关于某个窗口中所有数据预计何时到达管道的猜测。您也可以将其视为“我们永远不会看到具有更早时间戳的元素”。

数据源负责生成水印,并且每个 PCollection 都必须有一个水印来估计 PCollection 的完整程度。当水印前进到“无穷大”时,PCollection 的内容就完整了。通过这种方式,您可能会发现无界 PCollection 是有限的。水印在窗口结束时前进之后,任何具有该窗口内时间戳的后续元素都被视为迟到的数据

触发器 是一个相关的概念,它允许您修改和完善 PCollection 的窗口策略。您可以使用触发器来决定每个单独窗口何时聚合并报告其结果,包括窗口如何发出迟到的元素。

有关水印的更多信息,请参阅以下页面

触发器

在将数据收集和分组到窗口时,Beam 使用触发器来确定何时发出每个窗口的聚合结果(称为窗格)。如果您使用 Beam 的默认窗口配置和默认触发器,Beam 会在估计所有数据都已到达时输出聚合结果,并丢弃该窗口的所有后续数据。

在较高层面上,与在窗口结束时输出相比,触发器提供了两个额外的功能

  1. 触发器允许 Beam 在给定窗口中的所有数据都到达之前发出早期结果。例如,在经过一定时间后发出,或者在一定数量的元素到达后发出。
  2. 触发器允许通过在事件时间水印超过窗口结束时触发来处理迟到的数据。

这些功能允许您控制数据的流动,并在数据完整性、延迟和成本之间取得平衡。

Beam 提供了许多您可以设置的预构建触发器

有关触发器的更多信息,请参阅以下页面

状态和计时器

Beam 的窗口和触发器提供了一个抽象,用于根据时间戳对无界输入数据进行分组和聚合。但是,某些聚合用例可能需要更高程度的控制。状态和计时器是帮助解决这些用例的两个重要概念。与其他聚合一样,状态和计时器也按窗口处理。

状态:

Beam 提供了 State API 用于手动管理每个键的状态,允许对聚合进行细粒度控制。State API 允许您用可变状态来增强按元素的操作(例如,ParDoMap)。与其他聚合一样,状态也按窗口处理。

State API 对每个键建模状态。要使用 State API,您需要先从一个已键控的 PCollection 开始。处理此 PCollectionParDo 可以声明持久状态变量。当您在 ParDo 中处理每个元素时,可以使用状态变量来写入或更新当前键的状态,或读取之前为该键写入的状态。状态始终仅完全限定于当前处理键。

Beam 提供了几种状态类型,但不同的运行器可能支持这些状态的不同子集。

您可以将 State API 与 Timer API 一起使用来创建处理任务,这些任务可以为您提供对工作流的细粒度控制。

计时器:

Beam 提供了每个键计时器回调 API,它允许对使用 State API 存储的数据进行延迟处理。Timer API 允许您设置计时器,以便在事件时间或处理时间时间戳调用回调。对于更高级的用例,您的计时器回调可以设置另一个计时器。与其他聚合一样,计时器也按窗口处理。您可以将 Timer API 与 State API 一起使用来创建处理任务,这些任务可以为您提供对工作流的细粒度控制。

以下计时器可用

有关状态和计时器的更多信息,请参阅以下页面

可拆分的 DoFn

可拆分的 DoFn (SDF) 是 DoFn 的一个泛化,它允许您以非整体的方式处理元素。可拆分的 DoFn 使得在 Beam 中创建复杂、模块化的 I/O 连接器变得更容易。

常规 ParDo 一次处理整个元素,应用您的常规 DoFn 并等待调用终止。当您将可拆分的 DoFn 应用于每个元素时,运行器可以选择将元素的处理拆分为更小的任务。您可以检查元素的处理,并且可以拆分剩余的工作以产生额外的并行性。

例如,假设您要从非常大的文本文件中读取每一行。当您编写可拆分的 DoFn 时,您可以拥有单独的逻辑来读取文件的一部分,将文件的一部分拆分为子部分,并报告当前部分的进度。然后,运行器可以智能地调用您的可拆分的 DoFn,以并行地拆分每个输入并分别读取部分。

一个常见的计算模式包含以下步骤

  1. 运行器在开始任何处理之前拆分传入的元素。
  2. 运行器开始在每个子元素上运行您的处理逻辑。
  3. 如果运行器注意到某些子元素比其他子元素花费的时间更长,则运行器会进一步拆分这些子元素并重复步骤 2。
  4. 子元素要么完成处理,要么用户选择检查子元素,运行器重复步骤 2。

您也可以编写可拆分的 DoFn,以便运行器可以拆分无界处理。例如,如果您编写一个可拆分的 DoFn 来监视一组目录并输出到达的文件名,您可以拆分以细分不同目录的工作。这允许运行器拆分一个热门目录并为其提供额外的资源。

有关可拆分的 DoFn 的更多信息,请参阅以下页面

下一步

请查看我们的 其他文档,例如 Beam 编程指南、管道执行信息和转换参考目录。