如今,有充分的理由,流数据处理在大数据中已成为大问题。其中包括:

  • 企业渴望更及时地了解其数据,而切换到流是实现较低延迟的好方法。
  • 使用为此类永无止境的数据量设计的系统,可以更轻松地驯服在现代企业中越来越普遍的海量无边界数据集。
  • 在数据到达时对其进行处理,可以使工作负载随着时间的推移更均匀地分布,从而产生更一致且可预测的资源消耗。

尽管业务驱动对流的兴趣激增,但与批量生产的同类产品相比,流系统长期以来仍相对不成熟。只是直到最近,潮流才最终朝另一个方向摇摆。在我比较狂妄的时候,我希望这可能部分归因于我最初在“流101”和“流102”博客文章中提供了大量的技巧(本书的前几章显然基于此)。但实际上,看到流系统成熟,还有很多聪明而活跃的人喜欢构建流,这只是业界的兴趣所在。

在我看来,尽管争取一般流宣传的斗争已经取得了有效的胜利,但我仍将或多或少地提出来自“流101”的原始论点。一方面,即使许多行业已经开始关注这场战斗,它们在今天仍然非常适用。还有两个,那里还有很多人还没有拿到备忘录。本书是扩展这些观点的扩展尝试。

首先,我将介绍一些重要的背景信息,这些信息将有助于构想我要讨论的其余主题。我将在三个特定部分中执行此操作:

术语

要精确地谈论复杂的主题,需要对术语进行精确的定义。对于某些在当前使用中超载解释的术语,我将尽力明确我在讲这些术语时的意思。

能力

我评论了流系统常有的缺点。我还提出了我认为数据处理系统构建者需要采用的思路,以便满足未来现代数据消费者的需求。

时域

我介绍了与数据处理相关的两个主要时间域,展示了它们之间的关系,并指出了这两个域带来的一些困难。

术语:什么是流?

在继续之前,我想先解决一件事:什么是流?如今,流一词用于表示各种不同的事物(为简单起见,到目前为止,我一直在宽松地使用它),这可能会导致人们误解真正的流或实际的流系统能够。因此,我宁愿精确地定义该术语。

问题的症结在于,许多应该由它们描述的事物(无限制的数据处理,近似结果等)已经通过历史上的完成方式(即通过流执行引擎)被俗称地描述了。)。术语的缺乏精确性使流真正意味着什么,并且在某些情况下,这给流系统本身带来了负担,这意味着流系统的功能仅限于历史上被描述为“流”的特征,例如近似或推测结果。
鉴于精心设计的流传输系统具有与任何现有批处理引擎一样的能力(在技术上更多),可以产生正确、一致、可重复的结果,因此,我建议将“流传输”一词分离为非常具体的含义:

流系统

一种数据处理引擎,设计时考虑了无限的数据集。如果我想谈论低延迟,近似或推测性的结果,我会使用这些特定的词,而不是不精确地称它们为“流”。

在讨论人们可能遇到的不同类型的数据时,精确术语也很有用。从我的角度来看,有两个重要的(正交的)维度定义了给定数据集的形状:基数和构成。

数据集的基数决定其大小,基数最显着的方面是给定数据集是有限的还是无限的。这是我更喜欢用来描述数据集中的粗基数的两个术语:

有界数据

一种大小有限的数据集。

无界数据

一种数据集,其大小是无限的(至少在理论上是无限的)。

基数很重要,因为无限数据集的无限性质给使用它们的数据处理框架带来了额外的负担。下一节将对此进行更多介绍。

另一方面,数据集的构成决定了其物理表现形式。结果,构造定义了人们可以与问题进行数据交互的方式。在第六章之前,我们不会深入研究宪法,但为了让您简要了解事物,有两个重要的宪法:

数据集在特定时间点的整体视图。SQL系统通常以表格形式处理。

数据集随时间变化的逐元素视图。传统上,Map‐Reduce数据处理系统沿袭是流式处理的。

我们在第6章,第8章和第9章中非常深入地研究了流和表之间的关系,在第8章中,我们还学习了将时变关系联系在一起的统一底层概念。但是直到那时,我们主要处理流,因为那是构造管道开发人员直接与当今大多数数据处理系统(批处理和流)进行交互的地方。这也是宪法最自然地体现了流处理所特有的挑战。

关于流的极大限制

关于这一点,接下来让我们再谈一谈流系统可以做什么和不能做什么,重点是可以。我想在本章中谈到的最大的事情之一就是精心设计的流系统的功能。从历史上看,流系统一直局限于提供低延迟,不准确或推测性结果的小众市场,通常将其与功能更强大的批处理系统结合使用,以提供最终正确的结果。换句话说,就是Lambda架构。

对于尚未熟悉Lambda体系结构的用户,基本思想是将流系统与批处理系统一起运行,两者执行的基本相同。流式系统为您提供了低延迟,不准确的结果(由于使用了近似算法,或者因为流式系统本身不提供正确性),一段时间后,批处理系统开始运行并为您提供正确的输出。它最初是由Twitter的Nathan Marz(Storm的创建者)提出的,但最终获得了相当大的成功,因为它在当时确实是一个绝妙的主意。流引擎对于正确性部门来说有点令人失望,而批处理引擎本来就像您期望的那样笨拙,因此Lambda为您提供了一种吃蛋糕的方法。不幸的是,维护Lambda系统很麻烦:您需要构建,配置和维护管道的两个独立版本,然后以某种方式最后合并来自两个管道的结果。

作为花了很多年的时间在一个高度一致的流引擎上工作的人,我还发现Lambda体系结构的整个原理有点不好。毫无疑问,我是杰伊·克雷普斯(Jay Kreps)在“质疑Lambda建筑”一文发表时的忠实拥护者。这是针对双模式执行的必要性的首批高度可见的声明之一。愉快。Kreps在使用像Kafka这样的可重播系统作为流互连的情况下解决了可重复性的问题,甚至提出了Kappa架构,这基本上意味着要使用精心设计的系统来运行单个管道,适合手头的工作。我不认为该概念需要使用自己的希腊字母名称,但原则上我完全支持该想法。

老实说,我会更进一步。我认为精心设计的流系统实际上提供了批处理功能的严格超集。模数也许是效率的增量,但现在应该不需要批处理系统。Apache Flink员工深信这一想法,并建立了一个即使在“批处理”模式下也可以全天候流式传输的系统,这一点值得称赞。我喜欢它。

批处理和流处理效率差异
我提出的一个建议不是流系统的固有限制,而仅仅是迄今为止大多数流系统中设计选择的结果。批处理和流之间的效率差异主要是由于批处理系统中捆绑的增加和混洗传输效率的提高所致。现代批处理系统竭尽全力实现复杂的优化,这些优化使用令人惊讶的适度计算资源实现了惊人的吞吐量。没有理由可以将使批处理系统成为当今效率最重要的各种聪明见解的类型整合到为无限制数据设计的系统中,从而为用户提供我们通常认为的高延迟,高效率之间的灵活选择。批处理和低延迟,低效率的“流”处理。这实际上是我们在Google上使用Cloud Dataflow通过在同一统一模型下提供批处理和流式运行器而完成的工作。在我们的案例中,我们使用单独的运行器,因为我们碰巧有两个针对其特定用例进行了优化的独立设计的系统。从工程角度来看,从长远来看,我很乐意看到我们将两者合并为一个系统,该系统结合了两者的最佳部分,同时仍保持选择适当效率水平的灵活性。但这不是我们今天所拥有的。老实说,由于有了统一的数据流模型,它甚至不是绝对必要的;因此它可能永远不会发生。

所有这一切的必然结果是,流传输系统的广泛成熟以及用于无边界数据处理的强大框架相结合,将使Lambda体系结构及时地重新归因于其所属的大数据历史。我相信现在已经成为现实。因为这样做(也就是说,在自己的游戏中胜过对手),您实际上只需要两件事:

正确性

这使您与批处理相等。核心是正确性归结为一致的存储。流系统需要一种随时间检查持久状态的方法(Kreps在他的“为什么本地状态是流处理中的基本原语”一文中谈到),并且必须设计得足够好以在发生机器故障时保持一致。几年前,当Spark Streaming首次出现在公共大数据场景中时,这是一个在黑暗的流世界中保持一致性的灯塔。值得庆幸的是,此后一切都得到了很大的改善,但是值得注意的是,有多少流式系统仍然试图在没有强一致性的情况下获得成功。

重申这一点,因为这一点很重要:一次处理就需要强大的一致性,这是正确性所必需的,这对于任何有机会满足或超越批处理系统功能的系统都必不可少。除非您真的不在乎结果,否则我恳请您避免使用任何无法提供高度一致状态的流系统。批处理系统不需要您提前进行验证,如果它们能够产生正确的答案;不要将时间浪费在无法满足同一条件的流系统上。

如果您想了解更多有关在流系统中获得强大一致性所需的知识,建议您查看MillWheel,Spark Streaming和Flink快照论文。这三者都花费大量时间讨论一致性。鲁汶(Reuven)将在第5章中介绍一致性保证,如果您仍然渴望更多,那么在文献和其他地方都有关于此主题的大量质量信息。

时间推理工具

这使您超出了批处理范围。良好的时间推理工具对于处理事件时间偏斜不同的无界,无序数据至关重要。越来越多的现代数据集表现出这些特征,并且现有的批处理系统(以及许多流系统)缺乏必要的工具来应对它们带来的困难(尽管现在这是快速变化的,即使我写这篇文章也是如此)。我们将在本书的大部分内容中解释和关注这一点的各个方面。

首先,我们对时域的重要概念有了基本的了解,之后,我们将更深入地了解变化的事件时间偏斜的无界,无序数据。然后,我们将在本章的其余部分中讨论使用批处理和流式系统进行有界和无界数据处理的常用方法。

事件时间与处理时间

要说服无界限的数据处理,需要对所涉及的时间域有清楚的了解。在任何数据处理系统中,我们通常都会关注两个时间域:

活动时间

这是实际发生事件的时间。

处理时间

这是在系统中观察到事件的时间。

并非所有用例都关心事件的发生时间(如果您不关心事件的发生,那就太好了!您的生活会更轻松),但很多情况下都如此。示例包括表征一段时间内的用户行为,大多数计费应用程序以及许多异常检测类型,仅举几例。

在理想情况下,事件时间和处理时间将始终相等,事件发生时将立即进行处理。但是,现实并非如此,事件时间与处理时间之间的偏差不仅非零,而且通常是基础输入源,执行引擎和硬件的特征的高度可变的函数。可能影响偏斜程度的因素包括:

  • 共享资源限制,例如网络拥塞,网络分区或非专用环境中的共享CPU
  • 软件原因,例如分布式系统逻辑,争用等
  • 数据本身的特征,例如密钥分布,吞吐量差异或无序差异(即一架飞机,整个人都在整个飞行过程中都将手机离线使用,因而将他们从飞机模式中移出)

结果,如果您在任何实际系统中绘制事件时间和处理时间的进度,通常会得到一些类似于图1-1中红线的内容。
stsy_0101.png_
图1-1 时域映射。x轴表示系统中事件时间的完整性,也就是说,事件时间中的时间X可以观察到事件时间小于X的所有数据。y轴表示处理时间的进度,即数据处理系统执行时观察到的正常时钟时间。

在图1-1中,斜率为1的黑色虚线代表理想状态,其中处理时间和事件时间完全相等。红线代表现实。在此示例中,系统在处理时间开始时稍微滞后,在中间偏向理想位置,然后再次向末尾滞后。乍一看,此图中有两种类型的时滞可见,每种时滞都在不同的时域中:

处理时间

理想线和红线之间的垂直距离是处理时域中的滞后。该距离告诉您在给定时间发生事件与处理事件之间观察到的延迟时间(处理时间)。这也许是两个偏斜中更自然和直观的。

活动时间

理想线和红线之间的水平距离是管道中此时的事件时间偏斜量。它告诉您管道当前(在事件时间中)距离理想状态还有多远。

实际上,在任何给定时间点的处理时间滞后和事件时间偏斜都是相同的。它们只是看待同一事物的两种方式。关于滞后/偏斜的重要提示是:因为事件时间和处理时间之间的总体映射不是静态的(即,滞后/偏斜可以随时间任意变化时间),这意味着如果您关心数据的事件时间(即事件实际发生的时间),就不能仅在管道观察到数据的时间范围内分析数据。不幸的是,这是许多设计用于无限数据的系统历来运行的方式。为了应对无限制数据集的无限性质,这些系统通常提供一些窗口化输入数据的概念。我们稍后将对加窗进行更深入的讨论,但这本质上是指沿时间边界将数据集切成有限的片段。如果您关心正确性并有兴趣在事件时间的背景下分析数据,则无法像许多系统一样使用处理时间(即处理时间开窗)来定义这些时间边界。在处理时间和事件时间之间没有一致的关联,您的一些事件时间数据最终将在错误的处理时间窗口内结束(由于分布式系统固有的滞后性,许多类型的输入具有在线/离线特性)来源等),将正确性排除在窗口之外。我们在以下各节中的许多示例以及本书的其余部分中,将更详细地讨论这个问题。

不幸的是,在按活动时间进行窗口浏览时,图片也不完全是玫瑰色。在数据不受限制的情况下,无序和可变偏斜会引发事件时间窗口的完整性问题:在处理时间和事件时间之间缺乏可预测的映射,如何确定在给定事件时间内观察到所有数据的时间 X?对于许多现实世界的数据源,您根本无法做到。但是,当今使用的绝大多数数据处理系统都依赖于完整性的某些概念,这使得它们在应用于无限制数据集时处于严重的劣势。

我建议,与其尝试将无边界的数据整理为最终完成的有限信息批次,不如设计能够使我们生活在这些复杂数据集带来的不确定性世界中的工具。新数据将到达,旧数据可能会被收回或更新,我们构建的任何系统都应能够独自应对这些事实,完整性的概念是针对特定和适当用例的便捷优化,而不是语义上的优化。所有这些都有必要。

在详细介绍这种方法的外观之前,让我们结束另一个有用的背景知识:通用数据处理模式。

数据处理模式

至此,我们已经建立了足够的背景知识,可以开始研究当今有界和无界数据处理中常见的使用模式的核心类型。我们着眼于两种类型的处理,并在相关的情况下,在我们关注的两种主要类型的引擎(批处理和流式处理)的背景下进行研究,在这种情况下,我本质上将微批处理与流式处理混为一谈,因为两者之间的差异 在这个级别上,两者并不十分重要)。

有界数据

从概念上讲,处理边界数据非常简单,并且每个人都可能熟悉。在图1-2中,我们从左侧开始,那里有一个充满熵的数据集。我们通过诸如MapReduce之类的数据处理引擎(通常为批处理,尽管设计良好的流引擎也可以正常运行)运行它,并在右侧最终生成具有更大内在价值的新结构化数据集。
stsy_0102.png
图1-2 使用经典批处理引擎的有限数据处理。左侧的非结构化数据有限池通过数据处理引擎运行,从而在右侧产生了相应的结构化数据。

尽管作为该方案的一部分,您实际可以计算的内容当然会有无限的变化,但总体模型非常简单。更有趣的是处理无边界数据集的任务。现在,让我们看一下通常会处理无边界数据的各种方式,首先是传统批处理引擎所使用的方法,最后是您可以为无边界数据设计的系统所采用的方法,例如大多数流或微批处理引擎。

无界数据:批处理

批处理引擎虽然没有明确考虑到无边界数据的设计,但自从最初想到批处理系统以来,就一直使用批处理引擎来处理无边界数据集。如您所料,这些方法围绕将无边界数据切成适合批处理的有边界数据集组成。

固定窗户

使用批处理引擎重复运行来处理无边界数据集的最常见方法是,将输入数据窗口化为固定大小的窗口,然后将这些窗口中的每一个作为独立的有边界数据源进行处理(有时也称为翻转窗口),如图1-3所示。特别是对于诸如日志之类的输入源,事件可以写入其名称对应于其所对应的窗口的目录和文件层次结构中,这类事情一开始看起来就非常简单,因为您实际上已经执行了基于时间的随机获取数据提前进入适当的事件时间窗口。

但是,实际上,大多数系统仍然要处理完整性问题(如果由于网络分区而导致某些事件在发送到日志时被延迟,该怎么办?如果您的事件在全球范围内收集并且必须转移到处理之前的通用位置?如果您的事件来自移动设备该怎么办?),这意味着可能需要某种缓解措施(例如,延迟处理,直到您确定所有事件都已收集完毕,或者在给定窗口中重新处理整个批次)数据迟到)。
stsy_0103.png
图1-3 通过具有经典批处理引擎的ad hoc固定窗口进行无限制的数据处理。将无界数据集预先收集到有限的固定大小的有界数据窗口中,然后通过连续运行经典批处理引擎对其进行处理。

会话控制

当您尝试使用批处理引擎将无边界的数据处理为更复杂的窗口化策略(例如会话)时,这种方法会更加崩溃。会话通常被定义为由于不活动间隙而终止的活动时间段(例如,针对特定用户)。使用典型的批处理引擎计算会话时,通常会遇到将会话拆分为多个批次的情况,如图1-4中的红色标记所示。我们可以通过增加批量大小来减少拆分数量,但要以增加延迟为代价。另一个选择是添加额外的逻辑来拼接先前运行中的会话,但代价是更加复杂。
stsy_0104.png
图1-4 通过经典的批处理引擎通过专用的固定窗口将无限制的数据处理成会话。将无界数据集预先收集到有限的固定大小的有界数据窗口中,然后通过连续运行经典批处理引擎将其细分为动态会话窗口。

无论哪种方式,使用经典的批处理引擎来计算会话都不理想。更好的方法是以流方式建立会话,稍后我们将介绍。

无界数据:流式传输

与大多数基于批处理的无边界数据处理方法的即席性质相反,流系统是为无边界数据构建的。正如我们之前所讨论的,对于许多现实世界中的分布式输入源,您不仅发现自己正在处理无边界的数据,而且还会处理以下数据:

  • 就事件时间而言,是高度无序的,这意味着如果要分析发生事件的上下文中的数据,则需要在管道中进行某种基于时间的混洗。
  • 不同的事件时间偏斜,这意味着您不能仅仅假设在给定的事件时间X内,始终会在时间Y的恒定ε内看到大多数数据。

处理具有这些特征的数据时,可以采用几种方法。我通常将这些方法分为四类:时间不可知,近似,按处理时间进行开窗和按事件时间进行开窗。

现在,让我们花一些时间来研究每种方法。

时间不可知

与时间无关的处理用于时间基本上无关紧要的情况;也就是说,所有相关逻辑都是数据驱动的。由于有关这些用例的所有事情都是由更多数据的到来决定的,因此,除了基本数据传递之外,流引擎确实不需要支持任何特殊功能。结果,几乎所有现有的流系统都开箱即用地支持时间不可知的用例(当然,如果您在乎正确性,则可以保证一致性的模数间差异)。批处理系统也非常适合于无时间限制地处理无限制数据源,方法是将无限制源切成任意序列的有限制数据集,然后独立处理这些数据集。我们在本节中看几个具体的例子,但是考虑到处理时间不可知的处理过程的直接性(至少从时间的角度来看),我们将不会在上面花费更多的时间。

过滤。时间不可知处理的一种非常基本形式是过滤,图1-5给出了一个示例。假设您正在处理网络流量日志,并且想要过滤掉并非来自特定域的所有流量。您将查看到达的每个记录,查看它是否属于感兴趣的域,如果不属于,则将其删除。因为这种事情在任何时候都只依赖于一个元素,所以数据源是无界的,无序的以及事件时间偏斜不同的事实是无关紧要的。

stsy_0105.png
图1-5 过滤无限制的数据。各种类型的数据集合(从左向右流动)被过滤为包含单个类型的同类集合。

内部联接。另一个与时间无关的示例是内部联接,如图1-6所示。连接两个无边界数据源时,如果您只关心两个源中的元素到达时的连接结果,则逻辑上没有任何时间要素。看到一个来源的值后,您可以简单地将其缓冲到持久状态;仅在其他来源的第二个值到达后,才需要发出联接的记录。(实际上,您可能需要某种针对未忽略的部分联接的垃圾回收策略,这可能是基于时间的。但是对于很少或没有未完成联接的用例来说,这样的事情可能不是问题。)
stsy_0106.png
图1-6 对无边界数据执行内部联接。当观察到来自两个来源的匹配元素时,将生成联接。

将语义转换为某种外部联接会引入我们讨论过的数据完整性问题:看到联接的一侧后,如何知道另一侧是否会到达? 说实话,您没有,所以您需要引入一些超时概念,其中引入了时间元素。时间要素本质上是窗口化的一种形式,我们稍后将详细介绍。

近似算法

方法的第二大类是近似算法,例如近似Top-N,流k均值等。它们采用无限的输入源并提供输出数据,如果您斜视它们,它们或多或少看起来就像您希望获得的那样,如图1-7所示。近似算法的优点是,从设计上讲,它们的开销很低,并且设计用于无限制的数据。缺点是它们的数量有限,算法本身通常很复杂(这使得很难构想出新算法),并且它们的近似性质限制了它们的实用性。
stsy_0107.png
图1-7 计算无边界数据的近似值。数据通过复杂的算法运行,产生的输出数据或多或少看起来像另一侧的期望结果。

值得注意的是,这些算法通常在设计中确实需要时间(例如,某种内置的衰减)。并且由于它们在到达元素时对其进行处理,因此该时间元素通常基于处理时间。这对于在近似值上提供某种可证明的误差范围的算法而言尤其重要。如果这些误差范围是根据有序到达的数据确定的,则当您以不同的事件时间偏差为算法提供无序数据时,它们实际上没有任何意义。要记住的事情。

逼近算法本身是一个引人入胜的主题,但由于它们本质上是时间不可知处理的另一个示例(对算法本身的时间特征进行模运算),因此使用起来非常简单,因此在我们当前的情况下不值得进一步关注焦点。

加窗

其余两种用于无限制数据处理的方法都是窗口的变体。在深入探讨它们之间的差异之前,我应该弄清楚窗口化的确切含义,因为在上一节中我们只是简要地谈到了这一点。窗口化只是简单的概念,即获取一个数据源(无界或有界),并沿时间边界将其切成有限的块进行处理。图1-8显示了三种不同的窗口模式。

stsy_0108.png
图1-8 窗口化策略。每个示例都显示了三个不同的键,突出显示了对齐窗口(适用于所有数据)和未对齐窗口(适用于数据的子集)之间的差异。

让我们仔细看看每种策略:

  • 固定窗户(又名翻滚窗户)

我们之前讨论了固定窗口。固定的窗口将时间切成具有固定大小的时间长度的段。通常(如图1-9所示),将固定窗口的段均匀地应用于整个数据集,这是对齐窗口的一个示例。在某些情况下,最好对数据的不同子集(例如,每个键)进行窗口移相,以使窗口完成负载随着时间的推移更均匀地分布,这是未对齐窗口的一个示例,因为它们在数据中会有所不同。

  • 滑动窗口(又称跳窗)

固定窗口,滑动窗口的一般化由固定长度和固定周期定义。如果周期小于长度,则窗口重叠。如果周期等于长度,则您有固定的窗口。而且,如果周期大于长度,则您将拥有一种奇怪的采样窗口,该窗口仅查看一段时间内数据的子集。与固定窗口一样,滑动窗口通常是对齐的,尽管在某些用例中它们可以作为性能优化而未对齐。注意,图1-8中的滑动窗口是按原样绘制的,以提供滑动运动的感觉。实际上,所有五个窗口将应用于整个数据集。

  • 会话控制

动态窗口的一个示例是,会话由事件序列组成,这些事件序列以大于某个超时的不活动间隔终止。通过将一系列与时间相关的事件(例如,一次就座观看的一系列视频)组合在一起,会话通常用于分析一段时间内的用户行为。会话很有趣,因为它们的长度不能事先定义。它们取决于所涉及的实际数据。它们也是未对齐窗口的典型示例,因为会话实际上在不同的数据子集(例如,不同的用户)之间永远不会完全相同。

我们前面讨论的两个时间域(处理时间和事件时间)本质上是我们关心的两个时间。窗口在这两个域中都是有意义的,因此让我们详细研究每个域并了解它们之间的不同。由于处理时间窗口化在过去一直很普遍,因此我们将从这里开始。

按处理时间开窗。当按处理时间开窗时,系统实质上会将传入的数据缓冲到窗口中,直到经过了一定数量的处理时间为止。例如,对于五分钟的固定窗口,系统将缓冲数据五分钟的处理时间,然后将其在那五分钟内观察到的所有数据视为一个窗口,并将其发送到下游进行处理。
stsy_0109.png
图1-9 通过处理时间开窗进入固定窗口。数据根据它们到达管道的顺序收集到窗口中。

处理时间窗口化有一些不错的属性:

  • 这很简单。该实现非常简单,因为您不必担心会在一定时间内乱码数据。您只需在事物到达时缓冲它们,然后在窗口关闭时将它们发送到下游。
  • 判断窗口完整性很简单。由于系统对是否已看到窗口的所有输入具有完备的知识,因此可以对给定窗口是否完整做出完美的决策。这意味着在按处理时间进行窗口化处理时,无需以任何方式处理“后期”数据。
  • 如果您想推断有关来源的信息,那么处理时间窗口就是您想要的。许多监控场景都属于此类。想象一下,跟踪每秒发送到全局Web服务的请求数。为检测中断而计算这些请求的比率是对处理时间窗口的完美利用。

抛开优点,处理时间窗口化有一个很大的缺点:如果所讨论的数据具有与它们相关的事件时间,则如果处理时间窗口要反映何时发生的事实,则这些数据必须按事件时间顺序到达。这些事件实际上发生了。不幸的是,事件时间排序的数据在许多实际的分布式输入源中并不常见。

举一个简单的例子,想象一下任何收集使用情况统计信息以供以后处理的移动应用程序。对于给定移动设备离线任何时间(短暂的连接中断,在全国范围内飞行时的飞行模式等)的情况,在此期间记录的数据只有在设备再次联机后才会上传 。这意味着数据可能以几分钟,几小时,几天,几周或更长时间的事件时间偏差到达。在按处理时间窗口化时,从这样的数据集中绘制任何有用的推断基本上是不可能的。

再举一个例子,当整个系统运行良好时,许多分布式输入源似乎可以提供事件时间排序(或几乎如此)的数据。不幸的是,健康时输入源的事件时间偏斜很低,但这并不意味着它将一直保持这种状态。考虑一个全球服务,该服务处理在多个大洲收集的数据。如果跨带宽受限的跨洲网络(不幸的是,这很普遍)的网络问题进一步降低了带宽和/或增加了延迟,则突然您输入的一部分数据可能比以前有更大的偏斜。如果按处理时间对这些数据进行窗口化处理,则窗口将不再代表它们中实际发生的数据。相反,它们表示事件到达处理管道时的时间窗口,这是旧数据和当前数据的任意混合。

在这两种情况下,我们真正想要的是按照事件的时间对数据进行窗口化,从而对事件的到达顺序具有鲁棒性。我们真正想要的是事件时间窗口。

按事件时间开窗。当需要在有限的块中观察数据源时,可以使用事件时间窗口来反映这些事件实际发生的时间。这是开窗的黄金标准。在2016年之前,大多数使用的数据处理系统都缺乏对它的本机支持(尽管任何具有良好一致性模型的系统,例如Hadoop或Spark Streaming 1.x,都可以作为构建此类窗口系统的合理基础)。我很高兴地说,当今的世界看起来非常不同,从Flink到Spark到Storm到Apex都有多种系统,其本身就支持某种类型的事件时间窗口。

图1-10显示了一个将无界源窗口化为一小时固定窗口的示例。

stsy_0110.png
图1-10 通过事件时间进入固定窗口。数据根据发生的时间收集到窗口中。黑色箭头标出了到达处理时间窗口的示例数据,这些数据不同于它们所属的事件时间窗口。

图1-10中的黑色箭头标出了两个特别有趣的数据。每个事件到达的处理时间窗口与每个数据位所属的事件时间窗口不匹配。这样,如果针对关注事件时间的用例将这些数据放入处理时间窗口中,则计算结果将是不正确的。如您所料,事件时间正确性是使用事件时间窗口的一件好事。

关于在无限制数据源上进行事件时间窗口化的另一件好事是,您可以创建动态大小的窗口(例如会话),而在固定窗口上生成会话时无需观察到任何拆分(如我们在会话示例中先前所见) (请参阅第14页上的“无限制数据:流”),如图1-11所示。
stsy_0111.png
图1-11 通过事件时间进入会话窗口。数据被收集到会话窗口中,以根据相应事件发生的时间捕获突发事件。黑色箭头再次指出将数据放入其正确的事件时间位置所必需的时间上的随机播放。

当然,强大的语义很少免费提供,事件时间窗口也不例外。事件时间窗口有两个显着的缺点,这是因为窗口的生存时间(在处理时间中)通常必须长于窗口本身的实际长度:

正在缓冲

由于延长了窗口寿命,因此需要更多的数据缓冲。值得庆幸的是,持久存储通常是大多数数据处理系统所依赖的资源类型中最便宜的(其他类型主要是CPU,网络带宽和RAM)。因此,与使用任何设计良好,具有高度一致的持久性状态和良好的内存缓存层的数据处理系统相比,此问题通常比您可能想到的要少得多。而且,许多有用的聚合不需要缓冲整个输入集(例如,求和或平均),而是可以增量地执行,其中较小的中间聚合以持久状态存储。

完整性

鉴于我们通常没有什么好办法知道何时看到给定窗口的所有数据,那么如何知道何时可以实现窗口的结果呢?实际上,我们根本不这样做。对于许多类型的输入,系统可以通过诸如MillWheel,Cloud Dataflow和Flink(我们将在第3章和第4章中进一步讨论)中找到的水印之类的东西,给出窗口完成的合理准确的启发式估计。但是,对于绝对正确性至高无上的情况(再次考虑帐单),唯一真正的选择是为管道构建器提供一种方法,使其表达何时希望实现窗口结果以及如何随着时间的推移完善这些结果。处理窗口完整性(或缺乏窗口完整性)是一个引人入胜的话题,但是在具体示例的背景下也许是最好探索的一个问题,接下来我们来看一下。

概要

Whew!那是很多信息。如果您已经做到了这一点,那就值得推荐!但是我们才刚刚开始。在继续详细研究“光束模型”方法之前,让我们简要地回顾一下到目前为止所学到的知识。在本章中,我们完成了以下操作:

  • 澄清术语,重点关注“流”的定义,以指代考虑到无限制数据构建的系统,同时对通常归入“流”范畴的不同概念使用更具描述性的术语,例如近似/推测性结果。此外,我们强调了大型数据集的两个重要方面:基数(即有界与无界)和编码(即表与流),后者将占用本书下半部分的大部分时间。
  • 评估设计良好的批处理和流式处理系统的相对功能后,假定流式传输实际上是批处理的严格超集,并且随着流式传输系统的成熟,诸如Lambda体系结构之类的概念被认为不如批处理而被淘汰。
  • 提出了两个高级概念,这些概念是流系统赶上并最终超过批处理所必需的,分别是正确性和时间推理的工具。
  • 建立了事件时间与处理时间之间的重要差异,描述了差异在发生数据的上下文中分析时所带来的困难,并提出了从完整性的概念转向简单地适应随时间变化的数据的方法。
  • 通过批处理和流引擎研究了当今常用的绑定和无边界数据的主要数据处理方法,将无边界方法粗略地分类为:时间不可知,近似,按处理时间开窗和按事件开窗时间。

接下来,我们将深入研究Beam模型的细节,从概念上看我们如何在四个相关轴上分解数据处理的概念:什么,在哪里,何时以及如何。我们还将详细研究如何处理跨多个场景的简单、具体的示例数据集,重点介绍Beam模型支持的多个用例,并结合一些具体的API来使我们现实。这些示例将有助于带动本章介绍的事件时间和处理时间的概念,同时还探索水印等新概念。