原文:towardsdatascience.com/the-stream-processing-model-behind-google-cloud-dataflow-0d927c9506a0?source=collection_archive---------3-----------------------#2024-04-27

在无界数据处理中的正确性、延迟和成本平衡

https://medium.com/@vutrinh274?source=post_page---byline--0d927c9506a0--------------------------------https://towardsdatascience.com/?source=post_page---byline--0d927c9506a0-------------------------------- Vu Trinh

·发表于 Towards Data Science ·14 分钟阅读·2024 年 4 月 27 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/7d2d4818f0ca3e0538f82f7b4d3ded71.png

图片由作者创建。

本文最初发布于 https://vutr.substack.com

目录

  • 在我们继续之前

  • 论文中的介绍。

  • Dataflow 模型的细节。

  • 模型的实现与设计。

简介

Google Dataflow是一个完全托管的数据处理服务,提供无服务器统一的流式和批量数据处理。当处理流式数据工作负载时,它是 Google 推荐的首选服务。该服务承诺无论工作负载多大,都能确保正确性和延迟。为了实现这些特性,Google Dataflow 基于一个专用的处理模型——Dataflow,该模型源自 Google 多年来的研究和开发。本文是我在阅读论文后做的笔记:The Dataflow Model: A Practical Approach to Balancing Correctness, Latency, and Cost in Massive-Scale, Unbounded, Out-of-Order Data Processing。如果你想深入了解流式处理,我强烈推荐这篇论文。它包含了 Google 在引入 Dataflow 模型以应对其全球规模的流式数据处理需求过程中获得的所有经验和见解。尽管这篇论文写于 2015 年,但我相信它的贡献永不过时。

注意:本文发表于 2015 年,因此一些细节可能已经发生变化或更新。如果你有任何反馈或能够补充我博客内容的信息,欢迎评论。

在我们继续之前

为了避免更多的混淆

  • Dataflow是谷歌的流处理模型。

  • Apache Beam 允许用户基于 Dataflow 模型定义处理逻辑。

  • Google Cloud Dataflow是来自 Google Cloud 的统一处理服务;你可以认为它是 Apache Beam 管道的目标执行引擎。

工作流:你可以使用 Apache Beam 定义统一的处理逻辑,并决定将管道运行在你想要的执行引擎上,比如 Google Dataflow、SparkFlink等。

在深入探索 Dataflow 模型之前,以下几节将介绍一些背景信息、挑战和概念。

论文简介

在论文撰写时,像MapReduce及其“亲戚”如HadoopPigHiveSpark等数据处理框架允许数据消费者大规模处理批量数据。在流处理方面,像MillWheelSpark StreamingStorm等工具也开始支持用户。然而,这些现有模型在一些常见的用例中并未满足要求。

考虑一个例子:一个视频流媒体提供商的商业收入来自于向广告商收费,费用是根据广告观看量来计算的。他们想知道每天应向每个广告商收费多少,并汇总关于视频和广告的统计数据。此外,他们还希望对大量历史数据进行离线实验。他们希望了解他们的视频被观看的频率和时长,以及观看这些视频的内容/广告和观众的群体。所有这些信息都必须快速提供,以便在接近实时的情况下调整他们的业务。处理系统还必须简单且灵活,以适应业务的复杂性。他们还需要一个能够处理全球规模数据的系统,因为互联网使公司能够接触到比以往更多的客户。以下是谷歌一些人关于当时数据处理系统状况的观察:

  • 批处理系统如 MapReduce, FlumeJava (谷歌内部技术),以及 Spark 无法确保延迟 SLA,因为它们需要等待所有数据输入适配到批处理后才能进行处理。

  • 提供可扩展性和容错性的流处理系统,在表达力或正确性方面有所不足。

  • 许多系统无法提供精确一次语义,这会影响正确性。

  • 其他系统缺乏进行窗口处理所需的基本操作,或提供的窗口语义仅限于基于元组或处理时间的窗口(例如, Spark Streaming)*)

  • 大多数基于事件时间窗口的实现依赖于排序或具有有限的窗口触发条件。

  • MillWheel 和 Spark Streaming 足够可扩展、容错性强且低延迟,但缺乏高级编程模型。

他们总结了上述所有模型和系统的主要弱点是假设无界输入数据最终会完成。当面对今天庞大且高度无序的数据时,这种方法已经不再合理。他们还认为,任何解决多样化实时工作负载的方法必须提供简单但强大的接口,以根据特定的使用场景平衡正确性、延迟和成本。从这个角度来看,本文对统一流处理模型做出了以下概念性贡献:

  • 允许在无界、无序的数据源上计算事件时间顺序(事件发生时)的结果,并提供正确性、延迟和成本属性的可配置组合。

  • 在四个相关维度上分离管道实现:

  • 正在计算哪些结果?

  • 它们在事件时间中的计算位置。

  • 当它们在处理时间期间被具体化时,

  • 早期结果如何与后续改进相关?

  • 将数据处理的逻辑抽象与底层物理实现层分离,允许用户选择处理引擎。

在本博客的其余部分,我们将看到 Google 如何促进这一贡献。在我们进入下一部分之前,最后提一点:Google 指出,“这个模型没有什么神奇之处。” 这个模型并不会让你计算量大的任务突然加速;它提供了一个通用框架,允许简单表达并行计算,这并不依赖于像 Spark 或 Flink 这样的特定执行引擎。

无界/有界

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b600c3a3d1d9b71e57545ed30fd2fe74.png

图片由作者创建。

论文的作者使用“无界/有界”这个术语来定义无限/有限数据。他们避免使用流处理/批处理术语,因为这些术语通常意味着使用特定的执行引擎。无界数据指的是没有预定义边界的数据,例如,活跃电商应用的用户交互事件;数据流只有在应用不活跃时才会停止。而有界数据指的是可以通过明确的开始和结束边界来定义的数据,例如,从操作数据库导出的每日数据。

为了继续介绍部分,我们将回顾论文中使用的一些概念。

窗口化

组织者

窗口化将数据划分为有限的块。通常,系统使用时间概念将数据组织到窗口中(例如,过去 1 小时内的所有数据将属于一个窗口)。窗口中的所有数据作为一个组进行处理。用户需要对窗口抽象进行分组操作:聚合或时间限制操作,以处理无界数据。另一方面,一些对无界数据的操作不需要窗口概念,比如过滤、映射或内连接。窗口可以是对齐的,例如,应用于给定窗口的所有数据,或者是不对齐的,例如,仅应用于该窗口中特定数据子集的操作。窗口有三种主要类型:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/4e03f22f65b904cf7b97c9f9de7d290b.png

作者创建的图像。

  • 固定窗口: 窗口大小为静态定义,例如,按小时划分的窗口。

  • 滑动窗口: 窗口由窗口大小和滑动周期定义,例如,每 5 分钟开始的 30 分钟窗口。

  • 会话: 窗口捕捉数据子集中的一段活动期,在这种情况下,是按键捕捉。通常,它们通过超时间隔定义。

时间域

在处理与时间相关的事件数据时,需要考虑两个时间域:

  • 事件时间:事件本身发生的时间。例如,如果系统设备在 11:30 记录了你购买游戏物品,这个时间就被视为事件时间。

  • 处理时间:在处理过程中,事件在任何给定时刻被观察到的时间。例如,购买的游戏物品在 11:30 被记录,但仅在 11:35 到达流处理系统;这个“11:35”就是处理时间。

根据这个定义,事件时间永远不会改变,但处理时间会随着每个事件在管道步骤中流动而不断变化。这是在分析事件发生时刻时的一个关键因素。事件时间和处理时间之间的差异被称为时间域偏差。偏差可能由多种潜在原因引起,例如通信延迟或每个管道阶段处理时花费的时间。像水印这样的指标是可视化偏差的好方法。对于本文,作者考虑了管道处理过的事件时间的下水印。这些水印提供了一种概念,告诉系统:“在这个时间点之前的事件时间不会再出现在管道中。” 水印不仅用于观察时间域之间的偏差,还用于监控整体系统。在一个理想的世界中,偏差始终为零;我们可以在事件发生的第一时间就处理所有事件。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/4a1327f226be77c03f1911ae75999655.png

作者创建的图像。

在接下来的章节中,我们将学习数据流模型的细节。

核心原语

模型有两个核心转换操作,作用于 (key, value) 对;这两种转换都可以作用于有界和无界数据:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c4485cf3b3b6d6be67d4ab71ab77a2a4.png

图像由作者创建。

  • ParDo 用于通用并行处理。它将使用提供的用户定义函数(在 Dataflow 中称为 DoFn)处理每个输入元素,该函数可以为每个输入元素生成零个或多个输出。输入不需要是无界集合。

  • GroupByKey 用于基于定义的键进行分组操作。

ParDo 对每个元素进行操作,因此它可以转换为无界数据。GroupByKey 在将数据发送到下游步骤之前,会收集给定键的所有数据。如果输入源是无界的,那么无法定义它何时结束。标准解决方案是数据窗口化。

窗口化

支持分组的系统通常会重新定义其 GroupByKey 操作为 GroupByKeyAndWindow。作者在这方面的重要贡献是未对齐的窗口。第一个是将所有窗口化策略视为来自数据流模型的未对齐,并允许在需要时自定义调整以应用对齐的窗口。第二个是任何窗口化过程都可以分解为两个相关的操作:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/00c182d4cd7e9b73bb943724146758f5.png

图像由作者创建。

  • AssignWindows 将元素分配到零个或多个窗口。从模型的角度来看,窗口分配在每个窗口中创建组件的新副本。

  • MergeWindows 在分组时合并窗口。这允许在数据到达并被分组时,基于时间构建数据驱动的窗口。窗口合并作为 GroupByKeyAndWindow 操作的一部分进行。我们可以通过以下示例来更好地理解:

触发器与增量处理

虽然支持未对齐的窗口,事件时间窗口带来了另一个挑战:需要告诉系统何时发出窗口的结果,因为数据可能以无序的方式出现在管道中。使用事件时间进度指标(如上所述的水印)的初步解决方案存在一些缺点:

提醒一下,免得你滚动回去看:水印是一个指示器,告诉系统“在这个时间点之前没有更多的事件时间较早的数据会出现在管道中。”例如,在给定时间,水印为“11:30”,这意味着不再会有事件时间早于 11:30 的数据出现。

  • 它们有时太快:这种行为意味着延迟数据可能会落后于水印。

  • 它们有时太慢:这种行为可能导致整个管道被延迟,等待一个缓慢的数据点。

这导致了以下观察:仅使用水印决定何时发出窗口的结果可能会增加延迟(当水印较慢时),或影响管道的准确性(如果水印过快,可能会漏掉一些数据)。作者在 Lambda 架构中观察到(该架构有两个独立的管道,流式和批处理,两个管道的结果最终会汇聚在一起),该范式并没有通过更快地提供正确答案来解决完整性问题;相反,它提供了来自流式管道的低延迟结果估算,然后承诺通过批处理管道提供正确的结果。他们指出,如果我们希望在单个管道中实现相同的目标,我们需要一种机制,为任何给定的窗口提供多个面板(答案)。这个功能称为触发器,允许用户指定何时触发给定窗口的输出结果。这里有一个插图,帮助你理解触发器和 Lambda 架构中的语义之间的相似性。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/eb2b6fb1621bf6cb50d441b19e5f2a76.png

由作者创建的图像。

作者介绍的系统支持以下触发器实现:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a831282ccd30ace2528a98f6dbb9f062.png

由作者创建的图像。

  • 在如水印这样的完成估算时触发。

  • 在处理时间点触发。

  • 基于数据到达特征(如计数、字节、数据标记、模式匹配等)进行触发。

  • 支持使用循环、序列或逻辑组合(与、或)实现的组合。

  • 用户可以利用执行运行时的底层原语(例如,水印计时器、处理时间计时器)和外部信号(例如,数据注入请求、外部进度度量)来定义触发器。

除了控制系统何时发出窗口的结果外,触发机制还提供了一种方法,通过以下精细化模式控制给定窗口的面板(答案)之间的关系:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1b5901e09ce2e8247d282836691bfb0b.png

由作者创建的图像。

  • 丢弃: 在触发时,系统丢弃所有内容的窗口。后续结果与之前的结果没有关系。此模式在下游消费者需要各个触发器的值独立时非常有用。在数据缓冲的空间效率方面,这也是最有效的选项。

  • 累积: 在触发时,系统将窗口内容保持在持久状态;后续结果与之前的结果相关联。当下游消费者期望在接收同一窗口的多个结果时,用新结果覆盖旧值时,这种模式非常有用。这也是 Lambda 架构系统中使用的模式,其中流式管道输出低延迟结果,随后被批处理管道的结果覆盖。

  • 积累与撤回: 在触发时,除了积累语义外,发出的结果副本也会存储在持久状态中。当窗口在未来再次触发时,首先会发出对先前值的撤回,然后才是新值。

下一部分将描述 Google 如何实现和设计 Dataflow 模型。

实现

论文的作者表示,他们已经使用 FlumeJava 在内部实现了这个模型,这是一个 Java 库,使得开发、测试和运行高效的数据并行管道变得容易。MillWheel 作为底层的流执行引擎。此外,Google Cloud Dataflow 的外部重新实现主要在论文撰写时已经完成。有趣的是,核心的窗口和触发代码相当通用,批处理和流处理实现之间有很大一部分是共享的。

设计原则

Dataflow 模型的核心原则:

  • 永远不要依赖任何完整性的概念。

  • 灵活适应已知用例的多样性以及未来可能出现的用例。

  • 它不仅在每个预期执行引擎的上下文中有意义,而且还增加了价值。

  • 鼓励实现的清晰性。

  • 支持在数据发生的上下文中进行强有力的数据分析。

激励经验

在设计模型时,他们积累了与 FlumeJava 和 MillWheel 的实际经验。那些运作良好的部分会在模型中得到体现;那些不太理想的部分则会推动方法的改变。以下是一些影响设计选择的经验:

  • 统一模型: 这个设计选择的最初动机是,默认情况下一个巨大的管道以流模式在 MillWheel 上运行,但对于大规模回填有一个专门的 FlumeJava 批处理实现。另一个动机来自 Lambda 架构的经验,其中一个客户在 MillWheel 中运行流管道,并使用夜间的 MapReduce(批处理)生成真值。他们发现,随着时间的推移,客户逐渐不再信任管道之间弱一致性的结果。

  • 会话 是 Google 内部一个关键的使用案例。这个机制在许多场景中都得到了应用,包括搜索、广告、分析、社交媒体和 YouTube。任何关心在一段时间内关联用户活动波动的用户都会利用会话。因此,支持会话成为模型设计中不可或缺的一部分。

  • 触发器、累积与撤回: 两个在 MillWheel 上运行账单管道的团队遇到了问题,这些问题促使了模型的部分设计。那时的最佳实践是将水印作为完成度度量,并针对延迟数据使用额外的临时逻辑。由于缺乏更新和撤回系统,处理资源利用率统计的团队决定自行构建解决方案。另一个账单团队则遇到了由慢速数据处理单元引起的水印滞后问题(慢速单元影响整体作业完成性能)。这些不足成为了设计的重要推动因素,并使设计重点从追求完整性转向随时间适应性。这导致了两个决策:触发器,允许灵活指定何时生成结果,以及通过累积支持增量处理。

  • 水印触发器: 许多 MillWheel 管道计算聚合统计信息。大多数情况下,它们并不要求 100% 的准确性;它们关心的是在合理的时间内能够获得大致完整的数据视图。由于通过水印处理结构化输入源(如日志文件)时能够实现较高的准确性,客户发现水印在每个窗口触发单一、精确的聚合结果方面非常有效。

  • 处理时间触发器: 推荐管道使用处理时间定时器发出其输出。这些系统定期更新部分数据视图,比起等到基于水印的大致完整视图准备好,它们更具价值。这也意味着水印的概念不会影响其余数据输出的及时性。

  • 数据驱动和复合触发器: 用于追踪 Google 网页搜索趋势的异常检测管道中的不同检测系统促使了数据驱动触发器的设计。这些系统观察查询流并计算统计估计,以检查是否存在异常波动。当它们认为波动正在发生时,会发出开始记录;当它们认为波动已经停止时,会发出停止记录。这也成为了触发器组合的推动因素,因为实际上系统同时运行多个差异检测器,并根据一组逻辑多路复用输出。

Outro

在本周的博客中,我们讨论了数据流模型的设计原则和实现,该模型是著名的 Google Cloud Dataflow 服务背后的核心。如果你想深入了解该模型,我强烈推荐阅读这本书:流处理系统:大规模数据处理的“什么”、“哪里”、“何时”和“如何”,或者阅读论文作者之一的两篇博客:流处理 101流处理 102。希望我的工作能为那些想了解流处理世界的人带来一些价值。

下次博客见!

参考文献

[1] Google,数据流模型:在大规模、无界、无序数据中平衡正确性、延迟和成本的实用方法(2015 年)。

我的通讯是一封每周发布的博客风格邮件,在其中我记录我从比我聪明的人那里学到的东西。

所以,如果你想和我一起学习和成长,请在这里订阅: https://vutr.substack.com.

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐