数据流处理在大数据当中是越来越重要,有很好的理由。 其中:

(1) 业务渴望更及时的数据,并且切换到流是实现更低延迟的好方法。

(2) 在现代业务中越来越普遍的庞大的无限数据集使用为这种无休止的数据量设计的系统更容易处理。

(3) 随着时间的推移,处理数据会更加均匀,从而产生更一致和可预测的资源消耗(Processing data as they arrive spreads workloads out more evenly over time, yielding more consistent and predictable consumption of resources.)。

尽管这种业务需求驱动在流式处理中浪潮逐渐兴起,但与批处理相比,现存的流式处理系统仍然相对来说不成熟,这使得最近在这个领域产生了许多令人兴奋的,积极的发展。讨论流式处理,有些问题必须要先搞清楚:

这一篇文章将涵盖一些基本的背景信息,并在深入了解时域(time domains)的细节之前澄清一些术语,以及对批处理和流式处理的数据处理常见方法的高级概述。

1. 背景

首先,我将介绍一些重要的背景信息,这将有助于我构建将要讨论的其它主题。我们将在三个具体部分中进行:

(1) 术语:谈论复杂的话题要精确定义术语。对于某些在当前使用中有无法解释清楚(有歧义)的术语,我会尝试确定我所说的意思。

(2) 能力:我将介绍流式处理系统的缺点。我还将提出我认为数据处理系统建设者需要采纳的思路,以满足现代数据消费者的需求。

(3) 时间概念:我将介绍与数据处理相关的两个主要时间概念,展示它们之间的关系,并指出这两个概念的一些疑惑。

1.1 什么是流?

在进一步讨论之前,我们首先弄清楚一件事情:什么是流(streaming)? 这个术语在今天有不同的含义(为了简单起见,直到现我也是一直这样轻松简单的理解它),这可能会导致对理解什么是真正的流以及流系统能用来干什么产生误解。因此,这里我更精确一些地定义这个术语。

问题的关键在于许多东西应该用它们是什么来进行描述(例如,无限数据处理(unbounded data processing),近似结果(approximate results)等),但是却是通过如何实现进行描述它们(例如,通过流执行引擎)(have come to be described colloquially by how they historically have been accomplished (i.e., via streaming execution engines))。这种缺乏精准性的术语决定了流是什么,并且在某些情况下,带有这种负担(缺乏精准)的流系统,意味着它们的能力只能被限制在经常描述的特性上,诸如近似或推测性结果(burdens streaming systems themselves with the implication that their capabilities are limited to characteristics frequently described as “streaming,” such as approximate or speculative results)。考虑到设计良好的流系统与现在的批处理引擎一样都能够产生正确的,一致的,可重复的结果,所以我倾向于将这个术语理解为更具体的含 义:一种为无限数据集设计的数据处理引擎(a type of data processing engine that is designed with infinite data sets in mind)。(为了完整起见,可能值得一提的是,这个定义包括真正的流和微批量实现)。

至于其他常见描述(As to other common uses of streaming),这里我听到一些,每一个都使用了比较精准的术语进行描述,建议我们整个社区都应该尝试采用:

(1) 无限数据(Unbounded Data):一种不断增长的,实质上是无限的数据集。这些通常被称为流数据。然而,当使用(streaming)和(batch)来描述数据集时,这是有问题的,正因为如上所述,只是表示使用哪种执行引擎来处理数据集(译者注:streamingbatch可以理解为在处理数据的执行引擎上的表述)。所讨论的这两类数据集之间的关键区别实际上是它们的有限性,因此最好用能够描述它们之间区别的术语来表示它们。因此,我将无限的流数据集称为无限数据(unbounded data),将有限的批数据集称为有限数据(bounded data)。

(2) 无限数据处理(Unbounded data processing):一种持续处理数据的模式,应用在上面描述的无限数据。尽管我个人喜欢使用术语来描述这种类型数据的处理,但在这种上下文的情况下它又意味着使用流执行引擎,这很容易产生误导。批处理引擎的重复运行也可以用来处理无限数据,因为批处理系统是第一个被构想出来的(相反,设计良好的流处理系统同样能够处理有限数据上的'批'(handling 'batch' workloads over bounded data))。因此,为了清楚起见,我将简单地称之为无限数据处理。(译者注:流处理引擎和批处理引擎都能够处理无限数据,因此无限数据不能使用流streaming来描述)

(3) 低延迟,近似或推测结果(Low-latency, approximate, and/or speculative results):这些类型的结果通常与流处理引擎有关。批处理系统在设计之初就没有考虑到低延迟或推测性结果。当然,批量引擎完全可以产生近似的结果。因此,与上面的术语一样,将这些结果描述为它们是什么(低延迟,近似或推测)比通过它们是如何实现的(通过流式引擎)要好得多。

从这里开始,当我使用(streaming)这个术语时,你都可以假设我的意思是一个为无限数据集而设计的处理引擎。当我指的是上述其他术语时,我会明确地说出无限数据,无限数据处理或低延迟/近似/推测的结果。

1.2 streaming的局限性

接下来,让我们谈谈一下流处理系统可以做什么,不可以做什么,重点放在可以什么上; 我想在以前博客中遇到的最大的问题之一就是一个精心设计的流处理系统的性能如何。流处理系统长期以来一直被认为提供低延迟,不准确/推测结果,通常与功能更强大的批处理系统相结合,以提供最终的正确结果,即Lambda架构。

对于那些还不熟悉Lambda架构的人来说,Lambda的基本的思想就是,流处理系统与批处理系统一起运行,执行一样的计算。流处理系统为你提供低延迟,不准确的结果(或者是因为使用近似算法,或者是因为流处理系统本身不能提供正确性),一段时间后,批处理系统会为你提供正确的输出。最初由Twitter的Nathan Marz(Storm的创始人)提出,最终也相当的成功,因为事实上,在当时这是一个非常好的主意。对流处理引擎的正确性有些失望,批处理引擎实际上也没有你想象的那样笨重(unwieldy),所以Lambda给了你一个鱼与熊掌两者兼得的方法。不幸的是,维护Lambda系统非常麻烦:你需要构建,配置和维护两个独立版本的管道,然后以某种方式合并最后两个管道的结果。

作为一个花费了好几年时间研究一个强一致性流处理引擎的人,我也发现了Lambda架构的原理有点令人讨厌。毫不奇怪,我也非常赞同Jay KrepsLambda架构的质疑 博文的观点。这是第一个对双引擎执行必要性的远见陈述。Kreps在使用像Kafka这样的可重放系统作为流连接器的情况下解决了可重复性的问题,甚至提出了Kappa架构,这基本上意味着使用对手头作业设计良好的系统可以只运行一个管道。我虽然不相信这个概念需要一个新的名字,但我原则上完全支持这个概念。

说实话,我会更进一步认为,精心设计的流处理系统实际上可以提供比批量处理更多的功能。Modulo perhaps an efficiency delta,不会像现在这样存在批处理系统。对于Flink来说,这个想法是非常重要的,即使是在批处理模式下,也可以建立一个全时全流式系统(building a system that’s all-streaming-all-the-time under the covers, even in “batch” mode)。

1.3 事件时间 与 处理时间

为了说清楚无限数据处理,我们需要理解所涉及到的几个时间概念。在任何数据处理系统中,我们通常比较关心两个时间概念:

  • 事件时间,事件发生的时间。
  • 处理时间,即在系统中观察(observed)到事件的时间。

并不是所有的用例都关心事件时间,但是很多情况下是考虑事件时间的。例如,随着时间的推移分析用户行为,大多数计费应用程序以及许多类型的异常检测等等。

在理想的世界中,事件时间和处理时间总是相等的,事件在产生时就会被立即处理。然而事实并非如此,事件时间和处理时间之间的倾斜不仅是非零的,而且往往是一个与底层输入源,执行引擎和硬件特性相关的高度可变的函数。可能影响倾斜水平的事情包括:

  • 共享资源的限制,如网络拥塞,网络分裂或非专用环境中的共享CPU
  • 软件原因,如分布式系统逻辑,争用(contention)等
  • 数据本身的特点,包括密钥分配,吞吐量变化,或无序变化。

因此,如果你在现实世界的系统中绘制事件时间和处理时间的进度,则通常会得到类似于下图中红线:

X轴表示系统中的事件时间。Y轴表示处理时间,数据处理系统在执行时观察到的时钟时间。

斜率为1的黑色虚线表示理想情况下处理时间和事件时间完全相等,红线表示现实情况下对应关系。在这个例子中,系统在处理时间开始的时候稍微延迟了一些,在中间时候比较接近理想状态,后面又稍微延迟了一点。理想情况的黑线和现实情况的红线之间的水平距离是处理时间和事件时间之间的偏差。这个偏差本质上是由流水线处理引入的延迟。

由于事件时间和处理时间之间的偏差不是固定的,这意味着如果你关心它们的事件时间(即事件实际发生的时间),那么当它们出现在管道中时我们不能对其进行分析数据(译者注:单靠处理时间是不能知道事件时间的,即单靠处理时间是不够的)。不幸的是,大多数无限数据处理设计的时候只考虑了处理时间。为了处理无限数据集的无限性质,这些系统通常提供一些在输入数据上窗口的概念。我们将在下面深入讨论窗口,它实质上是沿着时间界线将数据集切成有限个片段。

如果你关心数据的正确性,并且对在事件时间上下文中进行数据分析感兴趣,那么不能像现在大多数系统那样使用处理时间(即处理时间窗口)来定义这些时间界线;在处理时间和事件时间没有一致性关系时,一些事件时间数据将会在错误的处理时间窗口中处理(由于分布式系统固有的延迟,许多类型的输入源的在线/离线特性,等等),从而导致计算不准确。我们将在下面的一些例子和下一篇文章中更详细地讨论这个问题。

不幸的是,即使按照事件时间划分窗口,也不能解决所有问题。在无限数据下,无序和可变偏差(variable skew)都会导致事件时间窗口的完整性问题:在处理时间和事件时间之间缺乏可预测的映射时,我们如何确定什么时候能观察到给定事件时间X的所有数据?对于许多现实世界的数据源,我们根本无法提完整性验证。目前使用的绝大多数数据处理系统都依赖于一些完整性的概念,这使得它们在处理无限数据集时就显得力不从心了。

我认为我们不应该将无限数据转化成有限批次数据(最终每个批次都是完整的),我们需要设计一个工具,使我们能够应对这些复杂数据集所带来的不确定性。新数据会到来,旧数据可能被删除或更新,我们设计的系统都应该能够独立应对这种情况,在这些系统中完备性的概念是一个辅助的优化,而不是语义上的必要条件。

在我们深入了解如何使用Cloud DataflowDataflow模型来构建这样一个系统之前,让我们先了解一个更有用的背景:通用数据处理模式。

2. 数据处理模式

在这个时候,我们已经了解了足够的背景知识,可以开始看一下有限和无限数据处理使用模式的核心类型。我们将看到两种类型的处理,以及相关的处理引擎(我们关心的是批处理和流式处理,在这种情况下,我将微批处理归到流处理中,因为两者之间的差异在这个层面上并不是非常重要)。

2.1 有限数据

处理有限数据比较简单的,而且大家都比较熟悉。在下图中,左边是一个杂乱无序的数据集。中间我们通过数据处理引擎(通常是批处理引擎batch,设计良好的流处理引擎streaming也可以处理)来处理数据,比如MapReduce,最后生成更有价值的结构化数据集:

我们更感兴趣的是处理无限数据集的任务。现在我们来看看通常处理无限数据的各种方式,先从传统的批处理batch引擎使用的方法开始,最后使用为无限数据设计的系统使用的方法,如常见的流式处理或微批处理引擎。

2.2 无限数据-batch

批处理batch引擎虽然不是为无限数据设计的,但是也已经被用来处理无限数据集,因为批处理系统是第一个被构想出来的。正如人们所期望的那样,这种方法围绕将无限数据分割成适合于批处理的有限数据集的集合。

2.2.1 固定窗口

使用批处理引擎的重复运行处理无限数据集的最常见方法是将输入数据窗口化为固定大小的窗口,然后将每个窗口作为单独的有限数据源进行处理。特别是对于像日志这样的输入源,事件可以写入目录和文件层次结构中,这些目录和文件的名称比较适合命名为对应的时间窗口(译者注:一个文件或者目录可以对应一个时间点的窗口),这样一眼看上去就显得比较简单了,我们需要做的就是基于时间重新洗牌(shuffle),提前将数据分配到对应的事件时间窗口内。

但实际上,大多数系统仍然会面临完备性的问题:如果由于网络分裂而导致某些事件延迟到达日志中,那该怎么办?如果你的事件是全局收集,并且在处理之前必须转移到一个共同的地点,那该怎么办?如果你的事件来自移动设备?这意味着可能需要采取某种缓解措施(例如,延迟处理,直到确保所有事件都已收集,或者只要有新数据到达就重新处理给定窗口的整个批次数据)。

2.2.2 会话

当你尝试使用批处理引擎将无限数据处理为更复杂的窗口策略(如会话)时,上述方法会比较糟糕。会话通常被定义为由不活动的间隔的活动时段(例如,针对特定用户)。使用传统的批处理引擎计算会话时,通常分割的会话会跨越多个批次(batch),如下图中的红色所示。可以通过增加batch大小来减少分割数量,但是以增加延迟为代价。另一个选择是添加额外的逻辑来拼接先前运行的会话(译者注:将断裂的会话通过逻辑处理拼接在一起),但代价是更复杂性的成本。

2.3 无限数据-streaming

与大多数基于批处理的无限数据处理方法的特殊性质相反,流式处理系统是为无限数据而设计的。正如我前面提到的,对于许多现实世界的分布式输入源,你发现不仅处理无限数据,而且数据还具有以下特点:

  • 相对于事件时间的高度无序,这意味着如果你想分析事件发生上下文中的数据,你需要在管道中进行某种基于时间的数据洗牌。
  • 事件时间的不同偏差,意味着你不能假设给定的事件时间X在一个常数时间Y上下浮动时间([X-Y, X+Y])内看到大部分的数据。

处理具有这些特征的数据时,可以采取一些方法。我通常将这些方法分为四类:

  • 与时间无关
  • 近似算法
  • 基于处理时间的窗口
  • 基于事件时间的窗口

现在我们将花一点时间来看看这些方法。

2.3.1 与时间无关

与时间无关(Time-agnostic)用于处理与时间不相关的情况 - 例如所有逻辑都是数据驱动的。因为关于这种场景的所有事情都是由数据到来决定的,所以除了基本的数据传输之外,流引擎确实没有什么需要特别支持的。因此,现有的流式处理系统基本上都支持这种与时间无关的场景(当然,对于那些关心正确性的人来说,模系统到系统(modulo system-to-system)的差异保证了一致性)。批处理系统也非常适用于无限数据源的与时间无关的使用场景,只需简单的将无限数据源分割为有限数据集合的序列并独立处理这些数据集合即可。考虑到Time-agnostic比较简单,我们将在本节中只看一些具体的例子,除此之外不会花费太多的时间。

2.3.1.1 过滤

time-agnostic处理一个比较基本的形式就是过滤。假设你正在处理Web流量日志,并且想要过滤掉不是来自特定域的流量。当每个记录到达时,看看它是否属于你感兴趣的域,并如果不是就过滤掉。由于这种情况在任何时候都只依赖于单一元素,所以即使数据源是无限的,无序的,以及不同的事件时间偏移都是无关紧要的。

2.3.1.2 内连接

另一个time-agnostic处理的例子是内连接(Inner-joins)。当两个无限数据源连接(join)时,如果只关心连接的结果,当一个元素从两个数据源到达时,逻辑中不用考虑时间因素(if you only care about the results of a join when an element from both sources arrive, there’s no temporal element to the logic)。一旦从一个数据源看到一个值,就简单地缓存在持久存储中;一旦第二个值从第二个数据源中到达,只需发送连接后的记录。

对于外连接来说将会引入了我们上述讨论的数据完备性问题:一旦你看到了连接的一边的元素,你怎么知道另一边的元素是否会到达? 我们不知道,所以我们必须引入超时概念,这同时也引入了时间元素。 这个时间元素本质上是一种窗口的形式,我们稍后会更仔细地看一下。

2.3.2 近似

第二种方法是近似算法,如Top-N近似算法,K-means流式算法等。他们接收无限输入数据源,而输出结果只能算是基本上满足我们的预期。近似算法的优点是,开销比较低,并且可以用于无限数据。缺点是算法本身往往很复杂,它们的近似性质限制了它们的应用。

值得注意的是:这些算法通常在其设计中有一些时间特征(例如某种内置的衰减因子)。而且,由于元素到达时才处理,所以时间特征通常都是基于处理时间的。这对于算法在它们的近似值上提供某种可控的误差范围尤为重要。如果这些误差范围在按顺序到达的数据上是可预测的,那么当你提供的是具有不同事件时间偏差的无序数据时,它们本质上是没有意义的(they mean essentially nothing when you feed the algorithm unordered data with varying event-time skew.)。

近似算法本身就是一个让热感兴趣的话题,但由于它们本质上是与时间无关处理的例子,它们使用起来相当简单,因此我们目前的关注点没有必要进一步进行探讨。

2.3.3 窗口

剩下的两种处理无限数据的方法都是窗口的变体。在深入探讨它们之间的差异之前,我应该明确地说明我说的窗口的意思,因为我只是简单地谈及了它。窗口就是将数据源(有限或者无限)沿着时间界线分割成有限数据块进行处理的一个简单概念。下图显示了三种不同的窗口模式:

  • 固定窗口:固定窗口将时间段划分为具有固定大小时间长度的段。通常(如上图所示),固定窗口的分段被统一应用于整个数据集,这是对齐窗口的一个例子。在某些情况下,希望对窗口进行移动处理不同数据子集(例如,每个密钥)。
  • 滑动窗口:固定窗口的一种广义形式(译者注:固定窗口是一种特殊的滑动窗口的),滑动窗口由固定长度和固定周期来定义。如果周期小于长度,则窗口重叠。如果周期等于长度,则为固定窗口。如果周期大于长度,则是一种采样窗口,只能查看一段时间内数据的子集。与固定窗口一样,滑动窗口通常是对齐的,尽管在某些使用情况下可能使用未对齐作为性能优化。请注意,上图中的滑动窗口被绘制为一种滑动运动的感觉;实际上,上述五个窗口都适用于整个数据集。
  • 会话窗口:动态窗口的一个例子,会话是由事件序列组成的,事件被大于某个超时时间不活跃的间隙终止(sessions are composed of sequences of events terminated by a gap of inactivity greater than some timeout)。会话通常用于分析用户行为。会话是有趣的,因为他们的长度不能提前定义;它们取决于所涉及的实际数据。它们也是未对齐窗口的典型例子,因为会话在不同的数据子集(例如,不同的用户)之间实际上不会相同。

有关时间的两个概念 - 处理时间事件时间 - 是我们关心的两个问题。窗口化在两个概念上都是有意义的,所以我们将详细看看每个概念,看看它们有何不同。由于处理时间窗口在现在系统中非常普遍,因此我将从它开始。

2.3.3.1 基于处理时间的窗口

当基于处理时间窗口化时,系统实质上将输入数据缓冲到窗口中,直到经过一定的处理时间。例如,在五分钟的固定窗口的情况下,系统将缓冲处理时间五分钟内的数据,之后将在那五分钟内观察到的所有数据视为在一个窗口内,并将它们发送到下游进行处理。

处理时间窗口有几个很好的特性:

  • 比较简单。实现起来非常简单,因为你不用担心根据时间进行数据重洗。当他们到达时,你只需将其缓存起来,并在窗口关闭时向下游发送。
  • 判断窗口的完备性比较简单。由于系统知道窗口内的所有输入数据是否都被看到,因此可以对给定的窗口是否完备做出完美的解答。这意味着在基于处理时间窗口时,不需要处理延迟数据。
  • 如果你想要根据观察到的去推断数据源信息,那么处理时间窗口就是你想要的。许多监控方案属于这一类。想象一下,跟踪发送到全球Web服务器上的每秒请求数量。计算这些请求的速率以检测中断是处理时间窗口的完美使用场景。

除了优点之外,处理时间窗口有一个非常大的缺点:如果所讨论的数据有与之相关的事件时间,那么如果处理时间窗口要反映这些事件实际发生的时间,那么这些数据必须以事件时间顺序到达。不幸的是,按事件时间有序的数据在许多现实世界的分布式输入源中是不常见的。

举一个简单的例子,想象任何收集使用统计信息以供日后处理的移动应用程序。当移动设备没有连接上网络的情况下,在该时段内记录的数据将不会被上传,直到设备再次连上网络。这意味着数据可能会以延迟几分钟,几小时,几天,几周或更长时间的事件时间到达。在使用基于处理时间的窗口时,从这样的数据集中都不可能得出任何有用的推论。

另一个例子是,当整个系统正常时,许多分布式输入源可能能提供事件时间有序(或非常接近)的数据。不幸的是,系统正常并不意味着输入源的事件时间偏差会一直保持比较低。考虑处理在多个大陆收集的数据的全球服务器。如果跨带宽限制的横贯大陆线路的网络问题(令人惊讶的是,这是令人惊讶的普遍现象)进一步降低了带宽和延迟时间,突然间,一部分输入数据可能会以比以前更大的偏差到达。如果你按照处理时间对数据进行窗口化,那么窗口不能代表实际发生的数据;相反,它们代表事件到达处理流水线的时间窗口,这是一些任意旧数据和当前数据的组合。

在这两种情况下,我们真正想要的是按照事件到达的顺序,按照事件时间对数据进行窗口化。其实我们真正想要的是基于事件时间的窗口。

2.3.3.2 基于事件时间的窗口

This diagram shows an example of windowing an unbounded source into one-hour fixed windows:

当你需要用反映事件实际发生时间的有限块来观察一个数据源时,你需要使用基于事件时间的窗口。这是窗口的黄金标准。令人遗憾的是,目前使用的大多数数据处理系统都缺乏本地支持(尽管任何带有不错的一致性模型的系统,如HadoopSpark Streaming都可以作为构建这种窗口系统的合理基础)。

下图显示了将一个无限数据源窗口化为一小时固定窗口的示例:

图中的白色实线表示两个我们感兴趣的特定数据。这两个数据都到达的处理时间窗口,与他们所属的事件时间窗口不匹配。因此,如果这些数据已经被窗口化到处理时间窗口,并且用户关心事件时间,那么计算结果将是不正确的。正如人们所期望的那样,事件时间正确性是使用事件时间窗口的一个好处。

without the arbitrary splits observed when generating sessions over fixed windows (as we saw previously in the sessions example from the “Unbounded data — batch” section):

关于无限数据源上的事件时间窗口的另一个好处是,你可以创建动态大小的窗口如会话窗口,而不会像在固定窗口生成会话时观察到跨batch的现象(正如上述在无限数据 - batch部分的会话示例中所见):

当然,天下没有免费的午餐,基于事件时间的窗口也不例外。基于事件时间的窗口有两个明显的缺点,窗口经常比窗口本身的实际长度要存活更长(在处理时间内)(windows must often live longer (in processing time) than the actual length of the window itself):

  • 缓存:由于窗口生命周期延长,需要缓存更多的数据。值得庆幸的是,持久性存储通常是大多数数据处理系统所依赖的最便宜的资源类型(其他主要是CPU,网络带宽和RAM)。至少比使用设计良好的具有强一致持久状态的数据处理系统和不错的内存缓存层要容易。而且,许多有用的聚合不需要缓存整个输入集合(例如,总和或平均),我们可以递增地执行,可以持久存储更少的中间聚合。
  • 完整性:鉴于我们通常没有很好的方法知道我们何时看到给定窗口的所有数据,以及我们如何知道窗口的结果何时可以发布? 事实上,我们根本就没有办法。对于许多类型的输入,系统可以通过类似MillWheelwatermarks(我将在第二部分中进一步讨论)给出一个合理准确的启发式窗口来完成估计。但是,在绝对正确性至关重要的情况下(再次考虑计费),唯一真正的选择是为管道建设者提供一种方法让系统自己控制实现的窗口何时发布结果,以及能让系统随着时间修改这些结果。

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

资料:http://blog.csdn.net/ccjhdopc/article/details/51121538

https://yq.aliyun.com/articles/73252?t=t1

results matching ""

    No results matching ""