Google Cloud Dataflow 背后的流式处理模型

Google Cloud Dataflow 背后的流式处理模型 原文towardsdatascience.com/the-stream-processing-model-behind-google-cloud-dataflow-0d927c9506a0?sourcecollection_archive---------3-----------------------#2024-04-27在无界数据处理中的正确性、延迟和成本平衡https://medium.com/vutrinh274?sourcepost_page---byline--0d927c9506a0--------------------------------https://towardsdatascience.com/?sourcepost_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、Spark、Flink等。在深入探索 Dataflow 模型之前以下几节将介绍一些背景信息、挑战和概念。论文简介在论文撰写时像MapReduce及其“亲戚”如Hadoop、Pig、Hive或Spark等数据处理框架允许数据消费者大规模处理批量数据。在流处理方面像MillWheel、Spark Streaming或Storm等工具也开始支持用户。然而这些现有模型在一些常见的用例中并未满足要求。考虑一个例子一个视频流媒体提供商的商业收入来自于向广告商收费费用是根据广告观看量来计算的。他们想知道每天应向每个广告商收费多少并汇总关于视频和广告的统计数据。此外他们还希望对大量历史数据进行离线实验。他们希望了解他们的视频被观看的频率和时长以及观看这些视频的内容/广告和观众的群体。所有这些信息都必须快速提供以便在接近实时的情况下调整他们的业务。处理系统还必须简单且灵活以适应业务的复杂性。他们还需要一个能够处理全球规模数据的系统因为互联网使公司能够接触到比以往更多的客户。以下是谷歌一些人关于当时数据处理系统状况的观察批处理系统如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.