超越批处理的世界——流式计算(1)

超越批处理的世界——流式计算(1)

本文来自我的个人博客 https://www.zhangshenghai.com/posts/58781/

作者:Tyler Akidau

译者:shenghaishxt

原文:https://www.oreilly.com/ideas/the-world-beyond-batch-streaming-101

如今,流式数据处理在大数据中十分重要,这里有几个原因,如下:

  • 商业竞争渴望更加及时的数据,而使用流计算能够有效地实现低延迟。
  • 大量的、无限的数据集在现代商业领域变得越来越常见,如果我们使用专门设计用来处理这些无限数据的系统,那么这样的处理会变得更加轻松。
  • 在数据到达前对其进行流式处理能够实现负载均衡,实现更好的一致性以及可预测的资源消耗。

尽管这样的商业驱动使得人们对于流式计算的兴趣大大增加,但是相对于已经在这个领域取得了许多令人激动的、积极的发展的批处理来说,流式计算仍然表现的不成熟。

对于一个在谷歌的大规模流式计算系统工作了超过五年的员工来说(曾经开发过MillWheel和Cloud Dataflow),我非常开心能够见证如今流式计算的热潮。我也对帮助人们理解关于流计算系统的方方面面以及如何最好地使用它感兴趣,尤其是在现有的批处理系统和流处理系统大多存在语义上的鸿沟的情况下。因此,O’Reilly的编辑邀请我写篇稿子谈谈我于2015年在Strata+Hadoop World伦敦大会上的演讲《对批处理说再见》的一些看法。由于我有相当多的内容需要叙述,因此我把它们分成两个部分:

  1. 流式计算(1):在深入讨论关于时间域和高层次数据处理(包括批处理和流处理)的细节之前,第一篇文章将会涉及一些基本的背景信息并且说清楚一些基础的术语。
  2. 数据流模型:第二篇文章主要包括云数据流模型使用的关于统一批处理和流式计算模型的速览,我们通过各种实际例子来进行解读。之后,我会将现有地批处理系统和流处理系统进行一个简洁的语义比较。

背景

在开始介绍前,我将会介绍一些重要的背景信息,这将会帮助我们理解剩余一些我们还未讨论的背景知识。我们将会分成三个不同的部分来讨论:

  • 术语:为了能够精确地谈论复杂的术语,我们需要对术语进行精确地定义。对于一些如今已经被滥用了的术语,在谈论到时我也会精确地定义它们。
  • 功能:我会对流式系统的一些经常性的缺点进行评论。我也会提出数据处理系统的建造者们需要的思维框架,从而解决现代数据消费者不断增长的需求。
  • 时间域:我将会介绍两个与数据处理相关的关于时间的主要概念,解释它们是如何联系,并且指出关于这两个时间域带来的难题。

术语

在进一步深入之前,我希望先解决一个问题:什么是流计算?如今我们使用“流计算”这个术语来表示着各种各样的事情(简单来说,在某种程度上我在很随意地使用它),这会导致我们对于流计算的误解,或者是我们不知道流计算到底能够做些什么。因此,我宁愿先定义好“流计算”。

问题的关键是,许多术语应该被描述为它们本该成为的样子(例如,无限数据处理,近似结果,等等),但却被描述成它们过去所被描述成的样子(例如,通过流计算执行的引擎)。缺乏在术语上的精确定义模糊了流计算真实的意义,在某些情况下,流计算系统本身还带有某种暗示,暗示着这种系统的能力被限制,常被冠以“流”的特征,例如近似或推测结果。鉴于设计良好的流计算系统与现有的批处理系统相比,同样拥有产生正确性、一致性和可重复性结果的能力,我更喜欢把“流计算”这个术语定义为一个非常明确的意义:一种设计时考虑了无限数据集的数据处理引擎。仅此而已。(为了实现完整性,值得强调的是这个定义包含了真正的流计算和微批处理的实现。)

至于关于流计算的其他一般的用途,这儿有一些我经常听到且每一个都有着更加具有精确性和描述性的术语,作为同一个社区,我建议我们应该采用这些术语。

  1. 无限数据:一种持续增长的、实际上是无限的一种数据集。它们经常被称为“流数据”。然而在应用于数据集的时候,流处理或批处理这样的术语是有问题的,如上所说,这就暗示着用某一种类型的执行引擎来处理那些数据。这两种类型的数据集之间的关键区别实际上是它们的有限性,所以最好用术语来描述它们从而研究这种区别。因此,我更喜欢把“流式”数据集称为无限数据集,并且把“批次”数据集称为有限数据集。
  2. 无限数据处理:一种不断向前发展着的数据处理模式,应用于前面所提及的无限数据。尽管我个人喜欢用“流”这个术语来描述这种类型的数据,但是在本文再次使用它意味着使用流式处理引擎,这是十分具有误导性的。自从批处理系统被构思出来以后,批处理引擎的循环处理就用于处理无限数据(相反,设计良好的流式系统比批处理系统更有能力处理有限数据)。所以为了使文章更加清晰,我简单将其称为无限数据处理。
  3. 低延迟、近似和/或推测结果:这种类型的结果经常与流式引擎联系在一起。在传统上,批处理系统并不是设计来实现低延迟、近似和/或推测结果的,但这并不是说它不可以。当然,批处理引擎更有能力得到近似结果。因此,在讨论了上列术语之后,再来描述这些结果是什么(低延迟、近似和/或推测),而不是通过历史表现(流式引擎)来描述。

在这之后,无论我在哪里用到“流计算”这个术语,你都能够理解为这是一种设计用来处理无限数据集的一种执行引擎,仅此而已。当我使用上述的任何术语时,我将会明确其是无限数据、无限数据处理还是低延迟、近似和/或推测结果。在云数据流中,这些是我们已经采用的术语,我鼓励大家使用相同的术语。

关于流式计算极其夸张的限制

下一步,让我们讨论流式系统可以做什么以及不可以做什么,重点是可以做什么。我希望实现的其中一件最重要的事情是讨论一个设计良好的流式系统能够做什么事情。流式系统长期被认为是给市场提供低延迟、近似和/或推测结果,常常与批处理系统结合从而提供最终正确的结果。例如:Lambda架构。

对那些不熟悉Lambda架构的读者来说,其基本思想是在运行流处理系统的同时运行批处理系统,两者都执行本质上一样的计算。流处理系统提供低延迟、不精确的结果(要么是因为使用了近似算法,要么是因为流处理系统本身就不提供正确性),一段时间后批处理系统计算完成并且提供正确的输出。Lambda架构最初由推特的Nathan Marz提出,他是Storm的创始人,这种方法最后取得了很大的成功,事实上,这在当时的确是非常好的主意。不过,流处理系统在正确性方面有些令人失望,而批处理系统又是那样固有的不灵活,所以Lambda就给我们一套现成的方案解决这个问题。不幸的是,维护这个系统比较麻烦,你需要构建、规定和维护两套独立的管道,然后以某种方式融合这两个管道的最终结果。

当我在一个强一致的流引擎中工作了几年之后,我也发现整个Lambda架构是有些问题的。不出所料,当Jay Krep的《质问Lambda架构》未出版前,我就是它的狂热粉丝。下面是反对双模式处理系统的必要性的陈述理由之一。克雷普通过使用可重用系统,例如Kafka作为流计算的连接点来处理重复性问题。这意味着使用一个设计良好的系统来运行管道,处理Lambda的任务。我不认为这个概念本身需要一个名字,但是我原则上完全支持这个主意。

实话说,我希望更进一步。我认为设计良好的流式系统实际上提供一种严格的超集给批处理。模数也许是个效率增量,像今天这样存在着的批处理系统不再必要。Flink的员工基于这种想法建造了一个完全使用流计算的系统,甚至同时支持批处理模式。我爱它。

必然的结果是广泛成熟的流式系统结合了具有鲁棒性的框架能够应用于无限数据,同时,允许Lambda架构回到属于它的大数据历史进程中。我相信这个时刻会成为现实,我们只需要做到这两件事,就可以在批处理擅长的领域打败批处理:

正确性——这使流处理引擎和批处理引擎能够等同

本质上,正确性最终归结于一致的存储。流式计算系统需要一种检查长久一致性的方法(Kreps曾在他的文章——《为什么本地状态是流式处理的基础》中谈到过这个问题),由于机器故障仍然存在,这个系统必须被设计得足够好来保持一致性。几年前,当Spark流计算第一次出现在公众大数据领域中,它就像一座灯塔照亮了黑暗的流世界。幸运的是,从那以后情况改善了很多。但是仍然有许多流式系统在没有强一致性的情况下继续尝试取得成功。我完全无法相信“至多一次处理”这样的处理方式依然存在。

再次重申,以下原因十分重要:“只处理一次”这个标准需要强一致性,这是正确性的要求,对于有机会超越批处理系统的流式系统来说,这也是必须的。除非你真正不在意你的结果的正确性,否则我还是建议你避开任何不能提供强一致性的流式系统。如果批处理系统有能力计算出正确的结果,那么它也不会要求你提前检查和确认,别把时间浪费在那些达不到这种能力的流式系统上。

如果你对于在流式系统中如何获取强一致性有疑问,我建议你查阅MillWheel和Spark Streaming里的相关论文。它们都花费了大量的篇幅讲解一致性。考虑到关于这个主题的信息也有大量的文献供参考,在下面的篇幅中我就不会再讨论它了。

时间推理工具——这使流处理引擎超越批处理引擎

优秀的时间推理工具对于无限、存在事件时间偏差的无序数据是重要的。越来越多的现代数据集显示出这样的特征,而现在的批处理系统(也包括大部分的流系统)缺乏必要的工具来解决这些特性带来的问题。我将会使用余下的篇幅以及下一章的大部分来重点介绍这个问题。

在开始之前,我们将会理解关于时间域的一些基本概念,之后我们将会深入理解无限、存在事件时间偏差的无序数据的概念。我将会花费本章剩余的篇幅来介绍批处理系统和流处理系统处理数据的一般方法。

事件时间和处理时间

对无限数据处理过程的进行强有力的说明需要对于时间域的清晰的理解。在任何数据处理系统中,我们主要关心以下两种时间域:

  • 事件时间,即事件实际发生的时间。
  • 处理时间,即系统中观察事件发生的时间。

不是所有的场景都需要关注事件时间(如果你不需要事件时间的话,那你的工作就轻松多了),但是大部分场景都是需要的。让我们举一些例子吧,比如说在一段时间内描述用户的行为,大多付费应用或者是许多类型的异常检测。

在理想的世界里,事件时间和处理时间永远是相等的,当事件发生的时候它就立即被处理了。然而事实并不是这么简单的,事件时间与处理时间的偏差不只是非零的,而常常是与输入源、执行引擎和硬件有关的一个可变化方程。以下因素能够影响偏差:

  • 共享资源的限制,例如网络拥塞、网络分区或者是非专用环境下的共享CPU。
  • 软件原因,例如分布式系统逻辑和争用等。
  • 数据本身的特征,例如密钥分配、吞吐率变化或无序变化(例如,乘客在飞机落地后才把手机由飞行模式调为正常模式)。

所以,如果你在任何真实世界系统中绘制关于事件时间和处理时间的处理过程图,你通常会得到如同图1中红线一样的内容。

image

图1:时间域对应的描述图。X轴代表系统中事件时间的完整性,即事件发生在某一刻之前所发生的所有事件。Y轴代表处理时间的过程,即数据处理系统中处理数据时系统的时间。

图中斜率为1的黑线代表理想状态下处理时间和事件时间是相等的,而红线代表着真实情况。在这个例子的初始阶段,处理时间在系统中有一定的延迟,随后在中期趋向于理想状态,而在最后阶段又出现了一些延迟。理想线与红线的水平距离即为处理时间和事件时间之间的偏差。本质上,这种偏差就是由处理管道所引入的延迟。

事件时间和处理时间之间的描述图并不是静态的,这意味着当在管道中观察它们时,如果你关心事件时间(如事件实际上发生的时间)的话,那么你就不能只分析管道中观察到的数据,即处理时间。不幸的是,这现有大多数无限数据处理系统分析数据的方法(即大多数无限数据处理系统都按照处理时间来设计)。为了解决无限数据集无穷的特性,这些系统常常提供一种窗口来将输入数据分块。我们将会在下面深度讨论如何进行分块,但本质上来说,它们都是将一大块数据集按照时间分成有限的块。

如果你在意正确性和数据中的事件时间,那么你不能用处理时间来定义那些数据边界(即处理时间窗口),但是仍然有许多现有的系统这样做。在处理时间和事件时间不存在一致性关联的情况下,有些按照事件时间分块的数据可能会被分到错误的处理时间窗口下(由分布式系统内在的延迟,或是许多在线/离线的数据源类型的延迟引起),在处理时间窗口下,正确性无法保证。我会在下面一篇文章中举大量例子,从而更加详细地讲解这个问题。

不幸的是,当使用事件窗口分块时,得出的图片似乎也不太乐观。在无限数据的环境下,无序和变化的偏差为事件时间窗口引入了完整性问题:在处理时间和事件时间之间缺少可预测的映射,在给定处理时间X的情况下,你如何决定是否已经观察到了所有的数据?对于大多数真实数据源来说,你没有办法。如今使用中的大多数数据处理系统都依赖于某种完整性的概念,但是在应用于无限数据集的时这样的缺点是十分严重的。

我的建议是不再尝试将无限数据分成有限次的最终具有完整性的数据,而是应该设计一种能够允许我们处理不确定的复杂数据的工具。当新数据到来时,老数据将会被撤销或更新,而且我们建造的任何系统都应该有能力解决这些情况,不断将完整性概念进行方便的优化,而不是语义上的需要。

在我们深入讨论如何使用Cloud Dataflow中的Dataflow Model建造这样的系统之前,让我们先了解一个更有用的背景概念:常用的数据处理模式。

数据处理模式

目前为止,我们已经拥有了足够的背景,足以让我们开始关注于如今有限数据处理和无限数据处理的常见的核心模型。我们将会在这两种引擎(流计算和批处理)的情况下,着重讨论这两种处理模式,我把微批处理模式与流处理归为一类。因为在这个层面上它们之间的差异并不是十分重要。

有限数据

处理有限数据是十分直接的,并且对于每个人来说都很熟悉。在下图中,我们首先从左边的非结构化的数据着手。我们通过一些数据处理引擎来处理它(典型的是批处理,不过设计优秀的流处理也可以完成得同样好,例如MapReduce),然后得到右边新的有着完好结构化的数据以及其内在的价值。

image

图2:使用经典批处理引擎处理有限数据。左侧的有限非结构化数据在经过数据处理引擎后,得到右侧对应的结构化数据。

当然,当你使用这个模型来进行计算的时候,当中会有无数种变化,但是总体来说这个模型是十分简单的。更有趣的是处理无限数据集的任务。现在让我们看看处理无限数据集的各种典型方式。我们从使用传统的批处理引擎的方法开始,最后以专门为无限数据集设计的系统所使用的方法结束,如流引擎或微批处理引擎。

无限数据——批处理

尽管批处理引擎并不是为无限数据量身定做的,但是自从批处理系统被构思出来时,批处理系统就一直用于处理无限数据集。我们可以想象,这样的方法将无限数据切分成一系列的有限数据集,以便其方便为批处理引擎处理。

固定窗口

最常见的处理无限数据集的方法多次重复将输入数据分割成一个个固定的窗口,然后将每一个窗口作为一个独立的、有限的数据源进行处理。特别是对于像日志这样的输入数据源,在这里事件能被记录到文件系统中的层级中,日志的名字就对应了它的窗口。其实在数据建立之前,系统实际上就已经基于事件时间把数据记录到对应的时间窗口中。

实际上,大多数系统仍然需要解决完整性问题:倘若由于网络的故障导致你的事件被延迟了怎么办?倘若你的事件在处理之前都要被传送到一个通用的地点怎么办?倘若你的事件是来自移动设备怎么办?这些情况都意味着我们需要用一些特别的方法处理它们(例如延迟处理事件直到你确认所有的事件都已经到达,或者当数据迟到时就在给定的窗口内对所有数据进行再次处理)。

image

图3:使用经典批处理引擎通过固定窗口处理无限数据。一个无限数据集被分为有限的、窗口固定的有限数据集,然后通过经典的批处理引擎来对其进行连续处理。

会话单元

如果你要用批处理引擎将无线数据划分为更加复杂的窗口(如会话单元),以上方法会失效。会话单元通常被定义为活动(例如特定的用户)的周期,以一段不活跃的时间来作为结束的标志。当使用经典的批处理引擎来计算会话单元时,你常常会看到会话单元被分到不同的批次中,如下图红色的标注所示。这些裂缝的数量可以通过增加批次的大小来减少,但这样做的话会增大延迟。另一个选择是增加额外的逻辑来拼接上一批的会话单元,但是这样会大大增加复杂度。

image

图4:使用经典批处理引擎通过固定窗口处理无限数据。一个无界的数据集被收集到有限的固定大小的有界数据窗口中,然后通过连续运行一个经典的批处理将这些有界数据划分为动态会话窗口。

无论使用哪种方式,使用批处理引擎计算会话窗口并不是十分理想的。更好的方法是以流的方式建立会话,我们将会在之后进行讲解。

无限数据——流式计算

与基于批处理的无限数据处理方法的临时特性相反,流系统专门为无限数据所建造。我先前说过,在大多数真实世界里分布式输入源中,你不仅仅需要解决无限数据的问题,还要解决:

  • 基于事件时间的高度无序性,这意味着如果你想要按照事件时间来分析数据的话,你需要处理时序问题。
  • 事件时间变化的时间差,这意味着在一个常量Y时间内,你无法假设看到对应给定的事件时间X内发生的所有数据。

以下有多种能够解决这样特性的数据的方法,我通常把它们归为以下几类:

  • 时间不可知
  • 近似算法
  • 按处理时间分片
  • 按事件时间分片

我们现在将花费一些时间来讲解每一种方法。

时间不可知

时间不可知处理本质上跟时间没有关系,其所有的关联逻辑是数据本身。这些情况下只关心更多数据的到达,因此并不需要使用流引擎对其进行特殊的支持,只需要保证基本的数据传送就足够了。所以,本质上所有的流系统都支持时间不可知(对于那些对于正确性有要求的场景来说,还需要排除不支持强一致的系统)。通过简单地将无限数据源切分成一系列有限数据集,然后独立地处理这些有限数据集,批处理系统也同样适用于处理时间不可知的无限数据源场景。我们将会讲解一组这个领域中的具体的例子,但由于处理时间不可知过程比较简单,我们不会在这上面花太多时间。

过滤

时间不可知处理过程的一个非常基础的形式是过滤。想象一个画面,你在处理网络流量日志,并且你想要过滤掉所有不来自某个特殊域的所有流量。那么你只要在每个记录到达时,判定其是否来自那个特殊域,来决定是留下还是丢弃。在任何时间内,它都只决定于数据本身,与数据源是否是无限的、是否是无序的以及事件的时间偏差没有任何关系。

image

图5:无限数据的过滤。从左侧流向右侧的不同类型的数据在经过过滤后,成为均匀的、只包含一种类型的数据。

内连接

另一种时间不可知的例子是内连接(又名哈希连接)。当连接两个无限数据源时,如果你只关心这两个无限数据源中共有的元素,那么它们的逻辑便是与时间无关的。当你从其中一个数据源中得到一个值后,只需要将它缓存在一个持久的缓存里,然后等到另外一个数据源也传来这个值,然后输出它们。(事实上,你可以会希望有一种垃圾回收装置来处理那些没有出现过的的与时间有关的连接元素。但是对于那些几乎不出现不完全连接的例子,这些就是小问题了。)

image

图6:对两个无限数据源执行内连接。在两个数据源中都观察到相同的匹配元素时,就执行内连接。

如果语义成为了外连接,那么之前说过的完整性问题又会出现:当你看到连接的一边,那你怎么能确定另外一边能否出现?我必须告诉你,你绝对不知道,所以你不得不引入某种超时装置,这就又涉及到了时间。这种时间本质是时间窗口分片,我们一会儿将会仔细研究它。

近似算法

image

图7:计算无限数据的近似值。数据经过复杂的算法,会产生看起来或多或少像另一侧的预期结果的输出数据。

第二类算法是近似算法,例如近似Top-N算法、流式K-means算法等等。它们都是输入无限数据,然后提供给你输出数据。这些近似算法的优点是在通过设计之后,它们的开销比较低,适合用于处理无限数据。但它们也有缺点,缺点是它们的数量有限,且实现复杂。近似的特性同时限制了它们的实用性。

值得一提的是,这些算法本质上是有一些时间域的特性(例如,某种衰退机制)。与此同时,这些方法一般都在数据到达之后进行处理,因此它们通常是用处理时间。对于那些能够提供证明错误范围的算法来说,这是十分重要的。如果算法能够通过数据到达的顺序来预测错误的界限,那么就算是事件-时间漂移有变化,对于无限数据来说都是可以忽略不计的了。这是需要注意的一点。

近似算法本来是一个很有趣的话题,但是本质上近似算法是一种时间不可知(如果不考虑它们自身的时间特征的话)。它们使用起来相当简单,所以我们不再详细介绍了。

时间窗口分片

其他两个无限数据处理的方法也是事件窗口分片。在深入介绍它们的差异之前,我会花一些时间来讲清楚时间窗口分片的具体含义。对于一个输入数据源来说(无论是有限还是无限),分片就是按照时间区间把数据分成有限片再进行处理。下图展示了三种不同的分片模式。

image

图8:不同的分片模式。每个例子都包括了三个不同的键,并且突出显示了窗口对齐(对于所有的数据都适用)以及窗口不对齐(只适用于数据子集)。

  • 固定窗口:固定窗口按照固定长度的时间进行分片。通常情况下(如图8所示),固定窗口的分段适用于所有数据集,这叫做对齐的窗口。在某些情况下,我们会希望对于不同的数据子集进行不同的相位偏移,从而让分片的完整度更加均匀。这就是非对齐窗口的一个示例,因为它们在数据之间变化。
  • 滑动窗口:滑动窗口可以看做是固定窗口更一般的一个形式。滑动窗口由两个量来定义:固定长度和固定周期。如果滑动时间比窗口小,那么窗口重叠。如果滑动时间等于窗口,那就是固定窗口。如果滑动时间比窗口大,那么就会出现一种奇怪的采样窗口,即按照时间来看数据集的一部分子集数据。类似于固定窗口,滑动窗口通常是对齐的。但是在某些情况下可能会不对齐,这是为了性能的优化。请注意,图8中的滑动窗口是为了给出滑动的感觉来绘制的;实际上,所有的五个窗口都适用于整个数据集。
  • 会话单元:它是动态窗口的一个实例。会话是在不活跃时间段的一连串事件,这个不活跃时间段通常比设定的超时时间长。会话单元通常用于将一系列与时间相关的事件(例如一次观看的视频序列)分组在一起来随时分析用户的行为。会话单元很有趣,因为它们的长度无法事先定义,这完全取决于涉及的实际数据。会话单元也是非对齐窗口的一个标准示例,因为在实际的情况下,不同子集数据的会话单元长度几乎不可能一致地对齐。

我们讨论的两个领域——处理时间和事件时间是我们关心的两个领域。窗口化在这两个领域都是有意义的,因此我们将详细地讨论每个领域,看看它们有什么不同。由于按照处理时间进行窗口分片是最普遍的,我们就从这里开始。

按照处理时间做时间窗口分片

image

图9:按照处理时间做时间窗口分片。根据它们到达管道的顺序将数据收集到窗口中。

当按照处理时间做时间窗口分片时,系统本质上是将输入的数据进行缓存,在经过一定的处理时间窗口之后再对缓存好的数据进行处理。例如,在一个5分钟的固定窗口中,系统会按照自己的系统时间缓存5分钟以内的数据,然后将这5分钟内的数据视为一个窗口,交由下一步的流程进行处理。

用处理时间窗口分片有如下几个很好的属性:

  1. 简单。实现起来十分简洁,你不用担心随着时间推移数据会失去顺序,只需要在到达时将数据缓存,并在窗口关闭时将它们发送到下一步即可。
  2. 判断窗口的完整性很简单。因为系统可以清楚地知道某窗口中的数据是否已经全部被看到,所以数据的完整性很容易保证。这意味着,当通过处理时间做时间窗口分片时,系统不需要以任何方式处理那些“迟到的”数据。
  3. 如果你关心的是事件被观察到后的信息,那么按照处理时间做时间窗口分片就正是你所需要的方法。很多监控场景都属于这一类。比如你希望可以获得某个网站的每秒请求量,再通过监控这个数量来判断网站是否有服务中断,这时用处理时间做时间窗口分片就是最好的选择。

除了这些优点之外,这种方法也存在一个巨大的缺点,即如果需要处理的数据具有与其相关的事件时间,而时间窗口需要反映数据的事件时间,那么这些数据就必须以事件时间的顺序到达了。不幸的是,现实世界中,按照事件时间排序并到达的数据几乎是没有的。

我们来举一个简单的例子,想象一个手机中的应用程序收集使用统计信息以供后期分析。当手机在离网一段时间后(比如离开网络、处于飞行模式等),这期间记录的数据就需要等到手机接入网络后才能够上传。这就意味着数据可能会以几分钟、几小时、几天、几周甚至更长的事件时间或者处理时间偏差到达。这时用处理时间做时间窗口分片就无法对这样的数据进行处理。

再举一个例子,许多分布式的数据源在系统正常的情况下能够提供有序的事件时间的数据(或是接近有序)。不幸的是,在系统健康得不到保证的时候就很难保证有序性了。比如某个处理多个大陆收集的数据的全球业务,洲际间的网络带宽一般都会受限(这是很常见的),这时突然间一部分的输入数据会比通常情况下更加晚到。如果继续使用处理时间对数据做时间窗口分片,就无法有效反映出数据实际发生时的情景。相反,这时窗口内的数据是一些任意组合的新旧数据。

在这两种情况下,我们想要按照事件到达的顺序按照事件时间进行时间窗口分片,这样才能保证数据到达的有序性。我们真正想要的是事件的时间窗口。

按照事件时间做时间窗口分片

当你需要将事件按照发生时的时间分进有限的块内,那么就需要用到事件时间窗口。这是时间窗口分片的黄金标准。令人遗憾的是,目前大多数数据处理系统都缺乏对齐的本地支持(尽管支持强一致的系统(如Hadoop或Spark Streaming)经过修改之后能够支持这种方法)。

下图显示了一个将无限数据按照事件时间分片的实例。

image

图10:按照事件时间用固定窗口分片。根据数据发生的时间将数据收集到窗口中。白色箭头将事件时间属于同一个分片的数据放到同一个窗口中去。

图中的白色箭头线对应着两个特别的数据。这两个数据都到达了处理时间窗口,但是与它们所属的事件时间窗口不匹配。因此,如果是按照处理时间来分片处理,但是我们关心的是事件发生时的信息,那么计算结果是不正确的。正如人们所期望的那样,用事件时间分片来保证事件时间计算的正确性是很好的。

这个方法来处理无限数据的另外一个好处就是你可以使用动态大小的窗口(如会话单元),不会出现在前面使用批处理引擎的时候,会话被分到两个窗口内的情况。

image

图11:按照事件时间窗口用会话单元做窗口分片。将数据按照它们发生的时间和活动性收集到不同的会话窗口内。白色箭头将那些属于同一个分片的数据放到同一个会话窗口中,按照正确的事件时间排序。

当然,强大的语义并不是免费的。按事件时间做时间窗口分片也不例外。由于窗口必须经常比窗口本身的实际长度长,所以事件时间窗口有两个明显的缺点:

  • 缓存:由于延长了窗口的使用寿命,需要更多的数据缓存。值得庆幸的是,持久性存储已经是大多数数据处理系统中最便宜的(其他的是CPU、网络带宽和RAM)。因此,这个问题没有想象中的那么严重。而且,许多有用的聚合不要求将整个输入集缓存起来(如总和、平均值),而是只要把中间的计算结果缓存下来然后递增地累积就可以了。
  • 完整性:考虑到我们往往无法判定是否已经收集到了一个窗口中的所有数据,那么我们如何知道什么时候才能将窗口中的数据交给下游去处理呢?事实上,我们根本就不知道。对于很多类型的输入,系统可以通过类似MillWheel的水印(我们将在第二部分详细讨论它)给出合理准确的完整性估计。但是对于正确性要求极高的场景中(如计费),唯一真正的选择是提供一个方法来让引擎决定什么时候交出数据,以及如何让系统不断地修正结果。处理窗口内数据(或者缺少窗口)的完整性是一个十分令人感兴趣的话题。但是最好能够在一个具体的例子中来讨论说明,我们下次再介绍。

结论

好吧!这篇包含太多信息了。如果你已经读到这里的话:你应该受到表扬!在这一点上,我想这些大概是我想介绍内容的一半。我们可以退一步,来回顾一下我们目前为止所学到的内容,并且在进入第二部分之前解决这些内容。尽管第一部分有些无聊,但令人兴奋的是,第二部分是乐趣真正开始的地方。

回顾

总结一下,目前我已经介绍了以下几点:

  1. 澄清术语,特别是将“流计算”的定义缩小为仅适用于执行引擎,同时使用了更多描述性术语,如将“无限数据”和近似/推测算法都放在流计算的概念下。
  2. 分析了精心设计的批处理系统和流计算系统,总结出流计算系统是批处理系统的功能超集,而像Lambda架构这样的概念最终会被流计算取代。
  3. 提出了流计算系统所需的重要的两个概念,能够帮助流计算追赶并最终超批处理:完整性和时间工具。
  4. 确定了事件时间和处理时间之间的重要差异,描述了这些差异在分析数据时出现的困难。根据完整性的概念,提出系统应该适应时间上的变化,提供完整、精确的结果。
  5. 分析了针对无限数据和有限数据的常用数据处理方法,主要通过批处理和流计算。并且将无限数据的处理分为四类:时间不可知、近似、通过处理时间进行窗口分片、通过事件时间进行窗口分片。

下一步的内容

本文提供了我们将在第二部分进行探讨的具体示例的基础。第二部分大致包含以下内容:

  • 从数据处理的概念上看,我们将从四个角度入手:什么、何处、何时以及怎么做。
  • 详细介绍如何在多个场景中处理简单、具体的示例数据集,突出显示数据流模型支持的多个用例以及涉及的具体API。这些例子将有助于推动本文介绍的事件时间和处理时间的概念,同时还将探索新的概念,如watermarks。
  • 比较现有的数据处理系统的重要特征,让我们更好地选择它们,并且鼓励大家对它们进行改善,帮助实现我们的最终目标:让流计算成为大数据处理的最好的方式。

现在是个好时机。回见!

你可能感兴趣的:(超越批处理的世界——流式计算(1))