Beam 模型基础
Apache Beam 是一个统一模型,用于定义批处理和流式数据并行处理管道。要开始使用 Beam,您需要了解一组重要的核心概念。
- 管道 - 管道是用户构建的转换图,定义了所需的数据处理操作。
- PCollection -
PCollection
是一个数据集或数据流。管道处理的数据是 PCollection 的一部分。 - PTransform -
PTransform
(或转换)表示数据处理操作,或管道中的一个步骤。转换应用于零个或多个PCollection
对象,并生成零个或多个PCollection
对象。 - 聚合 - 聚合是从多个(1 个或多个)输入元素计算一个值。
- 用户定义函数 (UDF) - 一些 Beam 操作允许您运行用户定义的代码作为配置转换的方式。
- 模式 - 模式是
PCollection
的语言无关类型定义。PCollection
的模式将该PCollection
的元素定义为有序的命名字段列表。 - SDK - 一个特定于语言的库,允许管道作者构建转换、构建他们的管道并将它们提交给运行器。
- 运行器 - 运行器使用您选择的數據處理引擎的功能运行 Beam 管道。
- 窗口 -
PCollection
可以根据单个元素的时间戳细分为窗口。窗口通过将集合划分为有限集合的窗口来实现对随时间增长的集合进行分组操作。 - 水印 - 水印是对何时预计某个窗口中的所有数据都已到达的猜测。这是必要的,因为数据不能总是保证按时顺序到达管道,也不能总是以可预测的间隔到达。
- 触发器 - 触发器决定何时聚合每个窗口的结果。
- 状态和计时器 - 每个键的状态和计时器回调是更低级的原语,使您可以完全控制聚合随时间增长的输入集合。
- 可拆分的 DoFn - 可拆分的 DoFns 允许您以非整体方式处理元素。您可以对元素的处理进行检查点,运行器可以拆分剩余的工作以产生额外的并行性。
以下部分更详细地介绍了这些概念,并提供了指向其他文档的链接。
管道
Beam 管道是所有数据和计算(特别是 有向无环图)在您的数据处理任务中的图。这包括读取输入数据、转换该数据和写入输出数据。管道由用户在其选择的 SDK 中构建。然后,管道通过 SDK 直接或通过运行器 API 的 RPC 接口到达运行器。例如,此图显示了一个分支管道
在此图中,框表示称为 PTransforms 的并行计算,带圆圈的箭头表示在转换之间流动的数据(以 PCollections 的形式)。数据可能是有限的、存储的、数据集,也可能是无限的數據流。在 Beam 中,大多数转换对有限数据和无限数据都适用。
您几乎可以将任何可以想到的计算表达式为图表示为 Beam 管道。Beam 驱动程序通常首先创建一个 Pipeline
对象,然后使用该对象作为创建管道的數據集和转换的基础。
有关管道的更多信息,请参阅以下页面
PCollection
PCollection
是元素的无序集合。每个 PCollection
都是一个潜在的分布式、同构数据集或数据流,并且由创建它的特定 Pipeline
对象拥有。多个管道不能共享一个 PCollection
。Beam 管道处理 PCollections,运行器负责存储这些元素。
PCollection
通常包含“大数据”(太多数据无法容纳在一台机器的内存中)。有时,一小部分数据或中间结果可能适合一台机器的内存,但 Beam 的计算模式和转换侧重于需要分布式数据并行计算的情况。因此,PCollection
的元素不能单独处理,而是以并行方式统一处理。
以下 PCollection
的特性很重要。
有限与无限:
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
。这些包括通用的核心转换,如 ParDo
或 Combine
。SDK 中还包含预先编写的复合转换,它们将一个或多个核心转换组合成有用的处理模式,例如在集合中计数或组合元素。您还可以定义自己的更复杂的复合转换,以适合管道的具体用例。
以下列表列出了一些常见的转换类型
- 源转换,如
TextIO.Read
和Create
。从概念上讲,源转换没有输入。 - 处理和转换操作,如
ParDo
、GroupByKey
、CoGroupByKey
、Combine
和Count
。 - 输出转换,如
TextIO.Write
。 - 用户定义的、特定于应用程序的复合转换。
有关转换的更多信息,请参阅以下页面
- Beam 编程指南:概述
- Beam 编程指南:转换
- Beam 转换目录(Java,Python)
聚合
聚合是根据多个(一个或多个)输入元素计算一个值。在 Beam 中,聚合的主要计算模式是对具有共同键和窗口的所有元素进行分组,然后使用关联和交换运算对每个元素组进行组合。这类似于 MapReduce 模型中的“Reduce”操作,但它得到了增强,可以处理无界输入流以及有界数据集。

图 1:元素的聚合。具有相同颜色的元素表示具有共同键和窗口的元素。
一些简单的聚合转换包括 Count
(计算聚合中所有元素的计数)、Max
(计算聚合中的最大元素)和 Sum
(计算聚合中所有元素的总和)。
当元素被分组并作为包发出时,聚合被称为 GroupByKey
(关联/交换操作是包并集)。在这种情况下,输出不小于输入。通常,您将应用一个操作,例如求和,称为 CombineFn
,其中输出明显小于输入。在这种情况下,聚合被称为 CombinePerKey
。
在实际应用中,您可能有数百万个键和/或窗口;这就是为什么这仍然是一个“令人尴尬地并行”的计算模式的原因。在您只有较少键的情况下,您可以通过添加辅助键来添加并行性,将您问题的每个自然键拆分为多个子键。在这些子键被聚合之后,结果可以进一步组合成您问题的原始自然键的结果。您聚合函数的结合性确保这会产生相同的结果,但具有更高的并行性。
当您的输入是无界时,根据键和窗口对元素进行分组的计算模式大致相同,但控制何时以及如何发出聚合结果涉及三个概念
有关可用聚合转换的更多信息,请参阅以下页面
- Beam 编程指南:核心 Beam 转换
- Beam 转换目录(Java,Python)
用户定义函数 (UDF)
一些 Beam 操作允许您运行用户定义的代码作为配置转换的一种方式。例如,当使用 ParDo
时,用户定义的代码指定要对每个元素应用的操作。对于 Combine
,它指定如何组合值。通过使用 跨语言转换,Beam 管道可以包含用不同语言编写的 UDF,甚至可以在同一个管道中使用多种语言。
Beam 有几种 UDF 变体
- DoFn - 每个元素的处理函数(在
ParDo
中使用) - WindowFn - 将元素放置在窗口中并合并窗口(在
Window
和GroupByKey
中使用) - ViewFn - 将物化的
PCollection
适应特定接口(在侧面输入中使用) - WindowMappingFn - 将一个元素的窗口映射到另一个,并指定结果窗口将回溯到过去多远(在侧面输入中使用)
- CombineFn - 关联和交换聚合(在
Combine
和状态中使用) - Coder - 编码用户数据;一些编码器具有标准格式,实际上不是 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
中的每个元素只能在一个窗口中,因此如果窗口函数为某个元素指定了多个窗口,则该元素在概念上会被复制到每个窗口中,并且每个元素除了其窗口外都是相同的。
聚合多个元素的转换,例如 GroupByKey
和 Combine
,在每个窗口的基础上隐式地工作;它们将每个 PCollection
视为多个有限窗口的连续,尽管整个集合本身可能具有无限大小。
Beam 提供了一些窗口函数
- 固定时间窗口(也称为“滚动窗口”)表示数据流中持续时间一致、不重叠的时间间隔。
- 滑动时间窗口(也称为“跳跃窗口”)也表示数据流中的时间间隔;但是,滑动时间窗口可以重叠。
- 每会话窗口定义包含在一定间隔时间内彼此相邻的元素的窗口。
- 单一全局窗口:默认情况下,
PCollection
中的所有数据都分配到单一全局窗口,并且会丢弃迟到的数据。 - 基于日历的窗口(Beam SDK for Python 不支持)
如果您有更复杂的需求,也可以定义自己的窗口函数。
例如,假设我们有一个使用固定时间窗口的 PCollection
,窗口长度为五分钟。对于每个窗口,Beam 必须收集所有事件时间时间戳在给定窗口范围内(例如,第一个窗口从 0:00 到 4:59)的数据。时间戳超出该范围的数据(来自 5:00 或更晚的数据)属于不同的窗口。
有两个概念与窗口密切相关,将在以下部分中介绍:水印 和 触发器。
有关窗口的更多信息,请参阅以下页面
水印
在任何数据处理系统中,数据事件发生的时间(“事件时间”,由数据元素本身的时间戳确定)与数据元素在管道中的任何阶段实际处理的时间(“处理时间”,由处理该元素的系统上的时钟确定)之间存在一定的延迟。此外,数据并不总是保证以时间顺序到达管道,或者总是以可预测的间隔到达。例如,您可能拥有不保留顺序的中间系统,或者您可能拥有两个服务器,它们都对数据进行时间戳,但其中一个具有更好的网络连接。
为了解决这种潜在的不可预测性,Beam 会跟踪水印。水印是关于某个窗口中所有数据预计何时到达管道的猜测。您也可以将其视为“我们永远不会看到具有更早时间戳的元素”。
数据源负责生成水印,并且每个 PCollection
都必须有一个水印来估计 PCollection
的完整程度。当水印前进到“无穷大”时,PCollection
的内容就完整了。通过这种方式,您可能会发现无界 PCollection
是有限的。水印在窗口结束时前进之后,任何具有该窗口内时间戳的后续元素都被视为迟到的数据。
触发器 是一个相关的概念,它允许您修改和完善 PCollection
的窗口策略。您可以使用触发器来决定每个单独窗口何时聚合并报告其结果,包括窗口如何发出迟到的元素。
有关水印的更多信息,请参阅以下页面
触发器
在将数据收集和分组到窗口时,Beam 使用触发器来确定何时发出每个窗口的聚合结果(称为窗格)。如果您使用 Beam 的默认窗口配置和默认触发器,Beam 会在估计所有数据都已到达时输出聚合结果,并丢弃该窗口的所有后续数据。
在较高层面上,与在窗口结束时输出相比,触发器提供了两个额外的功能
- 触发器允许 Beam 在给定窗口中的所有数据都到达之前发出早期结果。例如,在经过一定时间后发出,或者在一定数量的元素到达后发出。
- 触发器允许通过在事件时间水印超过窗口结束时触发来处理迟到的数据。
这些功能允许您控制数据的流动,并在数据完整性、延迟和成本之间取得平衡。
Beam 提供了许多您可以设置的预构建触发器
- 事件时间触发器:这些触发器基于事件时间运行,如每个数据元素的时间戳所示。Beam 的默认触发器是基于事件时间的。
- 处理时间触发器:这些触发器基于处理时间运行,即数据元素在管道中任何给定阶段处理的时间。
- 数据驱动触发器:这些触发器通过检查到达每个窗口的数据来运行,并在该数据满足某个属性时触发。目前,数据驱动触发器只支持在一定数量的数据元素到达后触发。
- 复合触发器:这些触发器以各种方式组合多个触发器。例如,您可能需要一个触发器用于早期数据,另一个触发器用于迟到的数据。
有关触发器的更多信息,请参阅以下页面
状态和计时器
Beam 的窗口和触发器提供了一个抽象,用于根据时间戳对无界输入数据进行分组和聚合。但是,某些聚合用例可能需要更高程度的控制。状态和计时器是帮助解决这些用例的两个重要概念。与其他聚合一样,状态和计时器也按窗口处理。
状态:
Beam 提供了 State API 用于手动管理每个键的状态,允许对聚合进行细粒度控制。State API 允许您用可变状态来增强按元素的操作(例如,ParDo
或 Map
)。与其他聚合一样,状态也按窗口处理。
State API 对每个键建模状态。要使用 State API,您需要先从一个已键控的 PCollection
开始。处理此 PCollection
的 ParDo
可以声明持久状态变量。当您在 ParDo
中处理每个元素时,可以使用状态变量来写入或更新当前键的状态,或读取之前为该键写入的状态。状态始终仅完全限定于当前处理键。
Beam 提供了几种状态类型,但不同的运行器可能支持这些状态的不同子集。
- ValueState:ValueState 是一个标量状态值。对于输入中的每个键,ValueState 存储一个类型化的值,可以在
DoFn
内部读取和修改。 - 状态的一个常见用例是将多个元素累积到一个组中
- BagState:BagState 允许您将元素累积到一个无序的包中。这样您就可以将元素添加到集合中,而无需读取任何先前累积的元素。
- MapState:MapState 允许您将元素累积到一个映射中。
- SetState:SetState 允许您将元素累积到一个集合中。
- OrderedListState:OrderedListState 允许您将元素累积到一个时间戳排序的列表中。
- CombiningState:CombiningState 允许您创建一个状态对象,使用 Beam 组合器进行更新。与 BagState 一样,您可以将元素添加到聚合中,而无需读取当前值,并且可以使用组合器压缩累加器。
您可以将 State API 与 Timer API 一起使用来创建处理任务,这些任务可以为您提供对工作流的细粒度控制。
计时器:
Beam 提供了每个键计时器回调 API,它允许对使用 State API 存储的数据进行延迟处理。Timer API 允许您设置计时器,以便在事件时间或处理时间时间戳调用回调。对于更高级的用例,您的计时器回调可以设置另一个计时器。与其他聚合一样,计时器也按窗口处理。您可以将 Timer API 与 State API 一起使用来创建处理任务,这些任务可以为您提供对工作流的细粒度控制。
以下计时器可用
- 事件时间计时器:当
DoFn
的输入水印超过设置计时器的时间时,事件时间计时器会触发,这意味着运行器认为没有更多具有比计时器时间戳更早时间戳的元素要处理。这允许进行事件时间聚合。 - 处理时间计时器:当实际挂钟时间过去时,处理时间计时器会触发。这通常用于在处理之前创建更大的数据批次。它也可以用于安排应在特定时间发生的事件。
- 动态计时器标签:Beam 还支持动态设置计时器标签。这允许您在
DoFn
中设置多个不同的计时器,并动态选择计时器标签(例如,基于输入元素中的数据)。
有关状态和计时器的更多信息,请参阅以下页面
可拆分的 DoFn
可拆分的 DoFn
(SDF) 是 DoFn
的一个泛化,它允许您以非整体的方式处理元素。可拆分的 DoFn
使得在 Beam 中创建复杂、模块化的 I/O 连接器变得更容易。
常规 ParDo
一次处理整个元素,应用您的常规 DoFn
并等待调用终止。当您将可拆分的 DoFn
应用于每个元素时,运行器可以选择将元素的处理拆分为更小的任务。您可以检查元素的处理,并且可以拆分剩余的工作以产生额外的并行性。
例如,假设您要从非常大的文本文件中读取每一行。当您编写可拆分的 DoFn
时,您可以拥有单独的逻辑来读取文件的一部分,将文件的一部分拆分为子部分,并报告当前部分的进度。然后,运行器可以智能地调用您的可拆分的 DoFn
,以并行地拆分每个输入并分别读取部分。
一个常见的计算模式包含以下步骤
- 运行器在开始任何处理之前拆分传入的元素。
- 运行器开始在每个子元素上运行您的处理逻辑。
- 如果运行器注意到某些子元素比其他子元素花费的时间更长,则运行器会进一步拆分这些子元素并重复步骤 2。
- 子元素要么完成处理,要么用户选择检查子元素,运行器重复步骤 2。
您也可以编写可拆分的 DoFn
,以便运行器可以拆分无界处理。例如,如果您编写一个可拆分的 DoFn
来监视一组目录并输出到达的文件名,您可以拆分以细分不同目录的工作。这允许运行器拆分一个热门目录并为其提供额外的资源。
有关可拆分的 DoFn
的更多信息,请参阅以下页面
下一步
请查看我们的 其他文档,例如 Beam 编程指南、管道执行信息和转换参考目录。
最后更新于 2024/10/31
您找到所需的一切了吗?
这些内容是否有用且清晰?您想更改什么吗?请告诉我们!