如今,流数据处理在大数据领域是一件大事,这是有充分理由的。其中:
l 企业渴望更及时的数据,而切换到流数据是实现更低延迟的好方法。
l 在现代商业中越来越常见的海量、无界数据集,使用专为这种无穷无尽的数据量而设计
的系统更容易控制。
l 在数据到达时对其进行处理,使工作负载在一段时间内更加均匀地分布,从而产生更加
一致和可预测的资源消耗。
尽管业务驱动对流数据的兴趣激增,但与批处理系统相比,现有的大多数流数据系统仍然相对不成熟,这导致最近该领域出现了许多令人兴奋的活跃开发。
作为一个在谷歌工作了五年多从事大规模流数据系统的人(MillWheel, Cloud Dataflow),我对这种流数据时代精神感到高兴,至少可以这么说。我也有兴趣确保人们理解流数据的所有功能,以及如何最好地使用它们,特别是考虑到大多数现有批处理系统和流数据之间仍然存在的语义差距。为此,O 'Reilly的好朋友们邀请我在2015年伦敦Strata + Hadoop世界上发表“告别批处理”的演讲。因为我有很多东西要讲,我将把它分成两篇文章:
l 流处理101:第一篇文章将介绍一些基本的背景信息,并澄清一些术语,然后深入讨论关于时域的细节,并对数据处理的常见方法(包括批处理和流处理)进行高层次的概述。
l Dataflow模型:第二篇文章将主要介绍Dataflow云所使用的统一批处理+流模型,并通过应用于不同用例集的具体示例进行讲解。在此之后,我将对现有批处理系统和流处理系统进行简短的语义比较。
所以,冗长的介绍结束了,让我们开始吧。
背景****
首先,我将介绍一些重要的背景信息,从而有助于我们接下来要讨论的主题。我们将氛围三个章节进行讨论:
l 术语:精确地谈论复杂的话题需要对术语有精确的定义。对于一些在当前使用中有过多解释的术语,我会试着在我说它们时准确地说明我的意思。
l 能力:我将评论流系统的常见缺陷。我还将提出我认为数据处理系统构建者需要采用的思维框架,以满足现代数据消费者的需求。
l 时间域:我将介绍与数据处理相关的两个主要时间域,展示它们之间的关系,并指出这两个域带来的一些困难。
术语:什么是流?****
在进一步讨论之前,我想先说明一件事:什么是流?“流”这个术语今天被用来表示各种不同的东西(为了简单起见,我到目前为止一直使用它有点松散),这可能会导致对流到底是什么或流系统实际上能够做什么有点误解。因此,我更倾向于对这个术语进行更精确的定义。
问题的关键在于,许多应该用它们是什么来描述的事情(例如,无界数据处理,近似结果等),已经通过它们在历史上是如何完成的来进行口头描述(即,通过流执行引擎)。这种术语上的缺乏精确性使流的真正含义变得模糊,并且在某些情况下,流系统本身的负担意味着它们的能力仅限于经常被描述为“流”的特征,例如近似的或推测的结果。考虑到设计良好的流处理系统与任何现有的批处理引擎一样能够产生正确的、一致的、可重复的结果,我更倾向于将流这个术语隔离为一个非常具体的含义:一种在设计时考虑无限数据集的数据处理引擎。仅此而已。(为了完整起见,可能有必要指出,这个定义包括真正的流处理和微批处理实现。)
至于“流”的其他常见用法,这里有一些我经常听到的,每一个都有更精确的描述性术语,我建议我们作为一个社区应该尝试采用:
1.无界数据:一种不断增长的、本质上无限的数据集。这些通常被称为“流数据”。然而,术语流或批处理在应用于数据集时是有问题的,因为如上所述,它们意味着使用某种类型的执行引擎来处理这些数据集。这两种数据集之间的关键区别实际上是它们的有限性,因此最好用能够捕捉到这种区别的术语来描述它们。因此,我将无限的“流”数据集称为无界数据,有限的“批”数据集称为有界数据。
2.无界数据处理:一种持续的数据处理模式,应用于上述无界数据类型。尽管我个人很喜欢使用流这个术语来描述这种类型的数据处理,但它在这种情况下的使用再次暗示了流执行引擎的使用,这充其量是一种误导;自批处理系统首次构想以来,批处理引擎的重复运行就被用于处理无界数据(相反,设计良好的流系统更能够处理有界数据上的“批处理”工作负载)。因此,为了清晰起见,我将其简单地称为无界数据处理。
3.低延迟、近似和/或推测性结果:这些类型的结果通常与流引擎相关。事实上,批处理系统在设计时并没有考虑到低延迟或推测性结果,这是一个历史工件,仅此而已。当然,批处理引擎完全能够根据指示产生近似的结果。
从这里开始,任何时候我使用术语“流”,您都可以放心地认为我是指为无界数据集设计的执行引擎,仅此而已。当我指的是上面的任何其他术语时,我将明确地说无界数据、无界数据处理或低延迟/近似/推测结果。这些是我们在云数据流中采用的术语,我鼓励其他人采取类似的立场。
关于流媒体被夸大的局限性****
接下来,让我们谈谈流系统能做什么和不能做什么,重点是能做什么;在这篇文章中,我想要传达的最重要的事情之一是,一个设计良好的流系统可以有多么强大。流系统长期以来一直被降级为提供低延迟、不准确/投机性结果的利基市场,通常与更强大的批处理系统结合使用,以提供最终正确的结果,即Lambda架构。
对于那些还不熟悉Lambda架构的人来说,其基本思想是在运行批处理系统的同时运行流处理系统,两者执行本质上相同的计算。流系统为您提供低延迟、不准确的结果(要么是因为使用了近似算法,要么是因为流系统本身不提供正确性),一段时间后,批处理系统滚动并为您提供正确的输出。最初由Twitter的Nathan Marz (Storm的创造者)提出,它最终非常成功,因为它在当时是一个非常棒的想法;流引擎在正确性方面有点让人失望,批处理引擎就像你所期望的那样固有地笨拙,所以Lambda给了你一种鱼龙混杂的方法。不幸的是,维护Lambda系统很麻烦:您需要构建、配置和维护管道的两个独立版本,最后还需要以某种方式合并来自两个管道的结果。
作为一个在高度一致的流引擎上工作了多年的人,我也发现Lambda架构的整个原则有点令人讨厌。毫不奇怪,当Jay Kreps的《Questioning the Lambda Architecture》发表时,我是它的忠实粉丝。这是反对双模式执行必要性的第一个非常明显的陈述之一;Kreps在使用像Kafka这样的可重玩系统作为流互连的背景下解决了可重复性问题,并提出了Kappa架构,这基本上意味着使用一个设计良好的系统运行一个单一的管道,该系统适合于手头的工作。我不相信这个概念本身需要一个名字,但我在原则上完全支持这个想法。
说实话,我会更进一步。我认为设计良好的流系统实际上提供了严格的批处理功能的超集。现在的批处理系统应该没有必要了。Flink团队将这一理念牢记于心,并构建了一个始终都是流的系统,甚至是在“批处理”模式下;我很喜欢。
所有这一切的必然结果是,流系统的广泛成熟与用于无界数据处理的健壮框架相结合,最终将Lambda架构降级为它所属的大数据远古时期。我相信现在是实现这一目标的时候了。因为要做到这一点,即替换批处理,你只需要两个东西:
1、正确性——这与批处理等价。从核心来说,正确性可以归结为一致性的存储。流式系统需要一种方法将持久化状态快照的方法(Kreps在他的文章《为什么本地化状态是流式处理的基础要素?》中谈到了这一点),并且它必须设计得足够好,以在机器故障时保持一致。几年前,当Spark Streaming首次出现在公共大数据领域时,它是一个黑暗的流处理世界中的一致性灯塔。值得庆幸的是,从那时起情况有所改善,但值得注意的是,许多流系统仍然试图在没有强大一致性的情况下运行;我真的不相信“最多处理一次”仍然是一种东西,但它确实是。重申一下,因为这一点很重要:只有一次的处理需要很强的一致性,这是正确性所必需的,这是任何有机会达到或超过批处理系统能力的系统的要求。除非你真的不关心你的结果,否则我恳求你避开任何不能提供强一致性状态的流系统。批处理系统不需要你提前验证它们是否能够生成正确的答案;不要把你的时间浪费在那些不能达到同样标准的流媒体系统上。如果你好奇如何在流系统中获得强大的一致性,我建议你看看MillWheel和Spark streaming的论文。两篇论文都花了大量时间讨论一致性。鉴于文献和其他地方关于这个主题的大量质量信息,我不会在这些帖子中进一步讨论它。
2、推理时间的工具——这让你超越了批处理。好的时间推理工具对于处理具有不同事件时间倾斜度的无界无序数据至关重要。越来越多的现代数据集表现出这些特征,而现有的批处理系统(以及大多数流系统)缺乏必要的工具来应对它们所带来的困难。我将用这篇文章的剩余部分和下一篇文章的大部分来解释和关注这一点。首先,我们将对时域的重要概念有一个基本的理解,之后我们将更深入地了解我所说的无界的、变化事件时间倾斜的无序数据的含义。接下来,我们将使用批处理和流处理系统,研究有界和无界数据处理的常用方法。
事件时间与处理时间****
要有说服力地谈论无界数据处理,需要对所涉及的时间域有清晰的理解。在任何数据处理系统中,我们通常关心两个时间域:
l 事件时间,即事件实际发生的时间。
l 处理时间,即在系统中观察事件的时间。
不是所有的用例都关心事件时间(如果你的用例不关心,那太好了!-你的生活更容易),但很多人这样做。例如,在一段时间内描述用户行为、大多数计费应用程序和许多类型的异常检测等。
在理想的情况下,事件时间和处理时间总是相等的,事件发生后立即进行处理。然而,现实并非如此,事件时间和处理时间之间的偏差不仅是非零,而且通常是底层输入源、执行引擎和硬件特征的高度可变函数。影响倾斜程度的因素包括:
l 共享资源限制,例如网络拥塞、网络分区或非专用环境中的共享CPU。
l 软件原因,如分布式系统逻辑、争用等。
l 数据本身的特征,包括key分发、吞吐量变化或无序变化(例如,一架飞机上的所有人,在整个飞行过程中脱离了飞行模式都在离线模式使用手机)。
因此,如果在任何实际系统中绘制事件时间和处理时间的进度图,通常会得到类似图1中红线的结果。
图1:时域映射示例。X轴表示系统中的事件时间完整性,即事件时间中的时间X,直到所有事件时间小于X的数据被观察到。y轴表示处理时间的进展,即数据处理系统在执行过程中观察到的正常时钟时间。图片来源:Tyler Akidau
斜率为1的黑色虚线表示理想状态,其中处理时间和事件时间完全相等;红线代表现实。在这个例子中,系统在处理时间的开始有一点延迟,在中间转向更接近理想状态,然后在接近结束时再次延迟。理想状态和红线之间的水平距离是处理时间和事件时间之间的倾斜。这种倾斜本质上是处理管道引入的延迟。
由于事件时间和处理时间之间的映射不是静态的,这意味着如果您关心它们的事件时间(即事件实际发生的时间),则不能仅在管道中观察到它们的上下文中分析数据。不幸的是,这是大多数为无界数据设计的现有系统的操作方式。为了处理无界数据集的无限性质,这些系统通常提供了对传入数据进行窗口处理的概念。我们将在下面深入讨论窗口,但它本质上是指沿时间边界将数据集分割成有限块。
如果您关心数据的正确性,并且对在其事件时间上下文中分析数据感兴趣,则不能像大多数现有系统那样使用处理时间(即处理时间窗口)定义这些时间边界;由于处理时间和事件时间之间没有一致的相关性,一些事件时间数据将在错误的处理时间窗口中结束(由于分布式系统中固有的滞后,许多类型的输入源的在线/离线性质,等等),可以说,将正确性抛到窗外。我们将在下一篇文章中以及下面的一些示例中更详细地讨论这个问题。
不幸的是,当按事件时间进行窗口时,情况也并不乐观。在无界数据环境中,无序和变量倾斜会导致事件时间窗口的完整性问题:在处理时间和事件时间之间缺乏可预测的映射,如何确定何时已经观察到给定事件时间X的所有数据?对于许多真实世界的数据源,您根本不能这样做。目前使用的绝大多数数据处理系统依赖于一些完整性概念,这使它们在应用于无界数据集时处于严重的劣势。
我建议,与其试图将无界的数据培养成最终完整的有限批信息,不如设计一些工具,让我们能够生活在这些复杂数据集带来的不确定性世界中。
在深入研究我们如何尝试用云数据流中使用的数据流模型构建这样一个系统之前,让我们先完成一个更有用的背景知识:公共数据处理模式。
数据处理模式****
至此,我们已经有了足够的背景知识,可以开始研究当今有界和无界数据处理中常见的核心使用模式类型。我们将在我们关心的两种主要引擎类型(批处理和流处理,在这种情况下,我基本上是将微批处理与流处理混为一谈,因为两者之间的差异在这个级别上不是特别重要)的上下文中讨论这两种类型的处理。
有 界 数据****
处理有界数据非常简单,可能大家都很熟悉。在下面的图表中,我们从左边的一个充满熵的数据集开始。我们通过一些数据处理引擎(通常是批处理,尽管一个设计良好的流引擎也可以工作)运行它,比如MapReduce,在右边结束了一个新的结构化数据集,具有更大的内在价值:
图2:使用经典批处理引擎的有界数据处理。左边的有限的非结构化数据池通过数据处理引擎运行,在右边产生相应的结构化数据。图片来源:Tyler Akidau。
当然,作为该方案的一部分,您可以实际计算的内容有无限的变化,但总体模型非常简单。更有趣的是处理无界数据集的任务。现在让我们看看无界数据通常处理的各种方式,首先是传统批处理引擎使用的方法,然后是为无界数据设计的系统可以采用的方法,例如大多数流处理或微批处理引擎。
无界数据批处理****
批处理引擎虽然在设计时没有明确考虑到无界数据,但自批处理系统首次被构想以来,就一直用于处理无界数据集。正如人们所期望的那样,这种方法围绕着将无界数据切片为适合批处理的有界数据集集合。
固定窗 口****
使用批处理引擎重复运行处理无界数据集的最常见方法是将输入数据设置为固定大小的窗口,然后将每个窗口作为单独的有界数据源处理。特别是对于像日志这样的输入源(其中事件可以写入目录和文件层次结构,其名称编码它们对应的窗口),这类事情乍一看似乎相当简单,因为您实际上已经执行了基于时间的混洗,以便提前将数据放入适当的事件时间窗口。
然而,在现实中,大多数系统仍然有一个完整性问题需要处理:如果由于网络分区,一些事件在到达日志的途中延迟了,该怎么办?如果事件是全局收集的,并且必须在处理之前转移到公共位置,该怎么办?如果事件来自移动设备呢?这意味着可能需要某种程度的缓解(例如,延迟处理,直到已经收集了所有事件,或者在数据延迟到达时重新处理给定窗口的整个批处理)。
图3:使用经典批处理引擎,通过特设固定窗口进行无界数据处理。一个无界数据集被预先收集到有限的、固定大小的有界数据窗口中,然后通过经典批处理引擎的连续运行进行处理。图片来源:Tyler Akidau。
会话****
当您试图使用批处理引擎将无界数据处理为更复杂的窗口策略(如会话)时,上述方法就更不适用了。会话通常被定义为一段活动时间(例如,对于特定用户),并在不活动的间隙结束。在使用典型的批处理引擎计算会话时,您经常会得到被分割到多个批次的会话,如下图中的红色标记所示。可以通过增加批处理大小来减少拆分的数量,但代价是增加延迟。另一种选择是添加额外的逻辑来缝合以前运行的会话,但代价是进一步增加复杂性。
无论哪种方式,使用经典的批处理引擎计算会话都不太理想。更好的方法是以流的方式构建会话,稍后我们将讨论这一点。
无 界 数据 - 流****
与大多数基于批处理的无界数据处理方法相反,流系统是为无界数据构建的。正如我前面提到的,对于许多真实世界的分布式输入源,您不仅会发现自己在处理无界数据,而且还会处理以下数据:
l 在事件时间方面高度无序,这意味着如果想要在发生事件的上下文中分析数据,就需要在管道中进行某种基于时间的随机操作。
l 不同的事件时间倾斜度,这意味着你不能假设你总是能在常数时间Y内看到给定事件时间X的大部分数据。
在处理具有这些特征的数据时,可以采用几种方法。我一般将这些方法分为四类:
l Time-agnostic
l 近似
l 按处理时间设置窗口
l 按事件时间设置窗口
Time-agnostic****
Time-agnostic用于处理对时间重要性要求不高的情况—即,所有相关逻辑都是数据驱动的。由于关于这些用例的一切都是由更多数据的到来决定的,因此除了基本的数据交付之外,流引擎实际上没有什么特别需要支持的。因此,基本上所有现有的流系统都支持开箱即用的时间不可知用例(当然,对于那些关心正确性的人来说,一致性保证中对系统到系统的方差取模)。批处理系统还非常适合无界数据源的时间无关处理,只需将无界数据源分割成任意有界数据集序列,然后独立地处理这些数据集。在本节中,我们将看几个具体的例子,但是考虑到处理与时间无关的处理的直接性,在此基础上就不多花时间了。
过滤****
与时间无关的处理的一种非常基本的形式是过滤。假设您正在处理Web流量日志,并且希望过滤掉所有不是来自特定域的流量。您将在每个记录到达时查看它,查看它是否属于感兴趣的域,如果不属于,则删除它。由于这类事情在任何时候都只依赖于单个元素,数据源是无界的、无序的和事件时间倾斜变化的事实无关紧要。
图5:过滤无界数据。不同类型的数据集合(从左到右流动)被过滤成包含单一类型的同构集合。图片来源:Tyler Akidau。
内连接****
另一个与时间无关的例子是内连接(或哈希连接)。在连接两个无界数据源时,如果您只关心来自两个数据源的元素到达时的连接结果,则在处理逻辑中无需考虑时间元素。当从一个数据源中看到一个值时,你可以简单地将它暂存到持久状态中;只有当来自其他源的第二个值到达时,才需要发送已连接的记录。(事实上,对于未发出的部分连接,您可能需要某种垃圾收集策略,这可能是基于时间的。但是对于很少或没有未完成连接的用例,这样的事情可能不是问题。)
图6:对无界数据执行内部连接。当观察到来自两个源的匹配元素时,将产生连接。图片来源:Tyler Akidau。
将语义切换到某种外部连接会引入我们已经讨论过的数据完整性问题:一旦您看到了连接的一侧,您如何知道另一侧是否会到达?说实话,你不知道,所以你必须引入一些超时的概念,这就引入了一个时间元素。时间元素本质上是一种窗口形式,我们稍后会更详细地讨论。
近似算法****
第二大类方法是近似算法,如近似Top-N,流K-means等。它们接受无限的输入源,并提供输出数据,如果你仔细观察它们,就会发现它们或多或少与你希望得到的结果相似。近似算法的优点是,从设计上讲,它们开销低,而且是为无界数据设计的。缺点是它们的数量有限,算法本身通常很复杂(这使得很难想出新的算法),而且它们近似的性质限制了它们的实用性。
值得注意的是:这些算法通常在设计中有一些时间元素(例如,某种内置的衰减)。由于它们在到达时处理元素,所以时间元素通常是基于处理时间的。这对于在近似上提供某种可证明的误差界限的算法尤其重要。如果这些错误边界是基于有序到达的数据而预测的,那么当您向算法提供具有不同事件时间倾斜的无序数据时,它们基本上没有任何意义。这是要记住的。
近似算法本身是一个迷人的主题,但由于它们本质上是时间不确定处理的另一个例子(对算法本身的时间特征取模),它们使用起来相当简单,因此鉴于我们当前的重点,不值得进一步关注。
窗口****
其余两种无界数据处理方法都是窗口的变体。在深入研究它们之间的区别之前,我应该清楚地说明我所说的窗口是什么意思,因为我只是简单地谈到了它。窗口只是一个概念,即获取一个数据源(无界或有界),并沿着时间边界将其切割成有限块进行处理。下图显示了三种不同的窗口模式:
图8:窗口策略示例。每个示例针对三个不同的键显示,突出显示对齐窗口(应用于所有数据)和未对齐窗口(应用于数据子集)之间的差异。图片来源:Tyler Akidau。
l 固定窗口:固定窗口将时间分割成固定长度的片段。通常(如图8所示),固定窗口的段在整个数据集上统一应用,这是对齐窗口的一个示例。在某些情况下,需要对不同数据子集的窗口进行相移(例如,每个键),以便在时间上更均匀地分散窗口完成负载,这是一个未对齐窗口的示例,因为它们在数据中是不同的。
l 滑动窗口:滑动窗口是固定窗口的泛化,滑动窗口由固定的长度和固定的周期来定义。如果周期小于长度,则窗口重叠。如果周期等于长度,则有固定窗口。如果周期大于长度,你就会有一种奇怪的采样窗口,它只关注一段时间内数据的子集。与固定窗口一样,滑动窗口通常是对齐的,尽管在某些用例中可能会为了性能优化而不对齐。请注意,图8中的滑动窗口是按原样绘制的,以提供滑动运动的感觉;实际上,所有五个Windows都适用于整个数据集。
l 会话:动态窗口的一个例子,会话由一系列事件组成,这些事件由大于某个超时的不活动间隙终止。会话通常用于分析用户在一段时间内的行为,通过将一系列与时间相关的事件分组在一起(例如,一次观看的一系列视频)。会话很有趣,因为它们的长度不能先验地定义;它们取决于所涉及的实际数据。它们也是未对齐窗口的典型例子,因为在不同的数据子集(例如,不同的用户)中,会话实际上从来都不相同。
以上讨论的两个时间域——处理时间和事件时间——本质上是我们所关心的两个。窗口在这两个领域都有意义,因此我们将详细了解每个领域,并了解它们的区别。由于处理时间窗口在现有系统中更为常见,因此我将从这里开始。
按处理时间设置窗口****
图9:按处理时间进入固定窗口。数据根据它们到达管道的顺序收集到窗口中。图片来源:Tyler Akidau。
当按处理时间设置窗口时,系统实际上是将传入数据缓冲到窗口中,直到经过一定的处理时间。例如,在5分钟固定窗口的情况下,系统将缓冲数据5分钟的处理时间,之后它将把在这5分钟内观察到的所有数据作为一个窗口,并将它们发送到下游进行处理。
处理时间窗口有几个很好的属性:
l 简单。实现非常简单,因为您从不担心在时间内打乱数据。你只需要在它们到达时进行缓冲,并在窗口关闭时将它们发送到下游。
l 判断窗口完整性很简单。由于系统完全了解一个窗口的所有输入是否已经被看到,因此它可以对给定的窗口是否完整做出完美的决定。这意味着在按处理时间设置窗口时,不需要能够以任何方式处理“后期”数据。
l 如果您希望根据所观察到的源推断有关它的信息,那么处理时间窗口正是您所需要的。许多监控场景都属于这一类。想象一下跟踪每秒发送到全球规模的Web服务的请求数。为了检测中断而计算这些请求的速率是处理时间窗口的完美使用。
除了这些优点之外,处理时间窗口还有一个非常大的缺点:如果所讨论的数据与事件时间相关,那么如果处理时间窗口要反映这些事件实际发生的时间,那么这些数据必须按照事件时间顺序到达。不幸的是,事件时间有序数据在许多真实世界的分布式输入源中并不常见。
作为一个简单的例子,假设有任何移动应用程序收集使用统计数据以供后续处理。在给定的移动设备离线的情况下(短暂的失去连接,在全国飞行时的飞行模式等),在这段时间内记录的数据将不会被上传,直到设备再次在线。这意味着数据到达时可能会出现分钟、小时、天、周或更长时间的事件时间偏差。当按处理时间进行窗口化时,从这样的数据集中得出任何有用的推论基本上是不可能的。
另一个例子是,当整个系统处于正常状态时,许多分布式输入源似乎提供了事件时间顺序(或非常接近于此)数据。不幸的是,当输入源处于正常状态时,事件时间倾斜较低的事实并不意味着它将始终保持这种状态。考虑一个处理从多个大陆收集的数据的全局服务。如果带宽受限的横贯大陆线路上的网络问题(不幸的是,这种情况非常普遍)进一步降低了带宽和/或增加了延迟,那么突然之间,您的一部分输入数据可能会开始以比以前更大的倾斜度到达。如果您通过处理时间对数据进行窗口化,那么您的窗口将不再代表其中实际发生的数据;相反,它们表示事件到达处理管道时的时间窗口,这是旧数据和当前数据的任意混合。
在这两种情况下,我们真正想要的是通过事件时间来显示数据窗口,这种方式对事件到达的顺序具有健壮性。我们真正想要的是事件时间窗口。
按事件时间设置窗口****
事件时间窗口是当您需要以有限块观察数据源时使用的方法,这些块反映了这些事件实际发生的时间。这是开窗的黄金标准。遗憾的是,目前使用的大多数数据处理系统都缺乏对它的本地支持(尽管任何具有良好一致性模型的系统,如Hadoop或Spark Streaming,都可以作为构建这样一个窗口系统的合理基础)。
下图展示了一个将一个无限资源窗口化到一个小时固定窗口的例子:
图10:按事件时间进入固定窗口。数据根据发生的时间收集到窗口中。白色箭头显示了到达处理时间窗口的示例数据,这些数据不同于它们所属的事件时间窗口。图片来源:Tyler Akidau。
图中的实线表示两个感兴趣的特定数据。这两个数据到达的处理时间窗口都与它们所属的事件时间窗口不匹配。因此,如果这些数据被窗口化到关心事件时间的用例的处理时间窗口中,那么计算结果将是不正确的。正如人们所期望的那样,事件时间正确性是使用事件时间窗口的一个好处。
在无界数据源上使用事件时间窗口的另一个好处是,你可以创建动态大小的窗口,比如会话,而不需要在固定窗口上生成会话时观察到任意分割(正如我们之前在“无界数据-批处理”部分的会话示例中看到的那样):
图11:按事件时间进入会话窗口。数据被收集到会话窗口中,根据相应事件发生的时间捕获活动爆发。白色箭头再次显示将数据放入正确的事件时位置所必需的时间洗牌。图片来源:Tyler Akidau。
当然,强大的语义很少是免费的,事件时间窗口也不例外。事件时间窗口有两个明显的缺点,因为窗口必须经常比窗口本身的实际长度更长(在处理时间上):
l 缓冲:由于窗口生存期延长,需要更多的数据缓冲。值得庆幸的是,持久存储通常是大多数数据处理系统所依赖的资源类型中最便宜的(其他主要是CPU、网络带宽和RAM)。因此,在使用任何设计良好、具有强一致持久状态和良好内存缓存层的数据处理系统时,这个问题通常比人们想象的要小得多。此外,许多有用的聚合不需要缓冲整个输入集(例如,求和或平均),而是可以增量地执行,将一个小得多的中间聚合存储在持久状态中。
l 完整性:鉴于我们通常没有很好的方法来知道我们何时已经看到了给定窗口的所有数据,那么我们如何知道窗口的结果何时可以实现呢?事实上,我们根本不知道。对于许多类型的输入,系统可以通过类似MillWheel的水印(我将在第2部分中详细讨论)之类的东西给出一个合理准确的启发式估计窗口完成情况。但在绝对正确性至关重要的情况下(再次考虑计费),唯一真正的选择是为管道构建器提供一种方式来表达他们什么时候想要实现窗口的结果,以及这些结果应该如何随着时间的推移而改进。处理窗口完整性(或窗口缺失)是一个迷人的主题,但最好在具体示例的背景下进行探索,我们将在下一节课中讨论。
结论****
唷!信息量可真大啊。对于那些已经走到这一步的人:你们是值得称赞的!至此,我们已经大致完成了我想要介绍的内容的一半,因此可能有理由后退一步,回顾一下我迄今为止所介绍的内容,在进入第2部分之前让事情稍微安定下来。所有这一切的好处是,第1部分是无聊的帖子;第2部分才是真正有趣的开始。
回顾****
总之,在这篇文章中,我有:
l 明确的术语,特别是缩小了“流”的定义,仅适用于执行引擎,同时使用更多描述性的术语,如无界数据和近似/推测结果,通常归类在“流”保护伞下的不同概念。
l 评估了设计良好的批处理系统和流处理系统的相对能力,假定流处理实际上是批处理的严格超集,并且像Lambda架构这样的概念,认为流处理不如批处理,随着流处理系统的成熟,这些概念注定要被淘汰。
l 提出了流系统追赶并最终超越批处理所必需的两个高级概念,分别是正确性和时间推理工具。
l 建立了事件时间和处理时间之间的重要差异,描述了这些差异在分析数据时所带来的困难,并提出了一种方法的转变,从完整性的概念转向简单地适应数据随时间的变化。
l 通过批处理和流引擎,研究了目前常用的有界数据和无界数据的主要数据处理方法,将无界方法大致分为:时间不可知、近似、按处理时间开窗和按事件时间开窗。
下次
这篇文章为我将在第2部分中探讨的具体示例提供了必要的上下文。将大致包括以下内容:
从概念上看,我们如何将数据流模型中的数据处理概念分解为四个相关轴:什么、在哪里、何时和如何处理。
详细介绍如何跨多个场景处理简单、具体的示例数据集,重点介绍数据流模型支持的多个用例以及涉及的具体api。这些例子将有助于理解本文中介绍的事件时间和处理时间的概念,同时还将探索新的概念,如水印。
比较两篇文章中涉及的重要特征的现有数据处理系统,以更好地在它们之间进行有根据的选择,并鼓励在缺乏的领域进行改进,我的最终目标是在整个大数据社区中改进数据处理系统,特别是流系统。
应该会很开心的。到时见!