小白学习Flink系列--第二篇-01(流式数据概念)

导读

要想彻底理解Flink,就要了解流数据的前世今生,流数据的语义、特点,以及如何处理,以下文章就能很好的解释流数据的概念和模型,对了解Flink有很大的帮助

前言

今天流式数据处理在大数据领域是一件大事,理由如下:

1、企业渴望更加及时的数据,而且采用流式处理是降低延迟的很好的办法。

2、在现代企业中大的且*的( unbounded)数据集变得更加普遍,且这些数据更容易被一个针对*数据设计的系统所使用。

随着时间的推移,和数据到达pipeline,数据处理的工作负载将更均匀地分布,从而产生更一致和可预测的资源消耗。

尽管是商业驱动激发了人们对流式系统的兴趣,但是相对于批处理系统而言,现存的大多数流式系统还不成熟,而近来在这个领域很多激动人心的进展·。

作为一个在谷歌从事大规模流式系统 (MillWheel, Cloud Dataflow)工作5年多的人,至少可以这么说,我为流式系统的时代思潮感到高兴。我对于如何帮助人们了解流式系统可以做什么以及怎样更好的使用流式系统很感兴趣,特别是考虑到现有批处理系统和流式系统之间的语义鸿沟。为了实现这个目标, 且OReilly的工作人员邀请我写一篇关于我之前两篇文章的续集: Say Goodbye to Batch 以及 Strata + Hadoop World London 2015

在此,我想说的有很多,所以我把它分成了两个部分:

Streaming 101: 第一篇文章将介绍一些基本的背景信息,并在深入了解时间域( time domains )的细节之前,先澄清一些术语,以及数据处理(批处理和流处理)中通用的方法和高级概念。

The Dataflow Model:第二部分主要介绍在Cloud Dataflow中使用的统一的批处理和流处理模型,通过一个具体的例子来进行深入, 在此之后,我将通过对现有批处理和流系统的简短语义比较来结束本文。

一、Background

首先,我将介绍一些重要的背景信息,这些信息将帮助构建我想要讨论的其他主题,主要针对三个特定的主题:

术语(Terminology): 要准确地谈论复杂的话题,需要对术语进行精确的定义, 对于那些当前已有很多语义的术语(在不同语境下有不同的含义的术语),我将尽量在提到它们的时候明确我所要表达的意思。

Capabilities:我将评论流式系统经常被人们诟病的缺点。 我也将提出我认为的数据处理系统构建人员为满足未来数据消费者的需求所需要采用的思路。

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

二、Terminology: What is streaming?

在进一步讨论之前,我想先讲一件事: what is streaming?“Streaming”这个词在今天可以指代很多不同的东西,这会导致我们对流式系统产生误解,或者说对流式系统究竟能做什么产生误解。 因此,我有必要稍微精确地定义这个术语。

问题的关键是,许多事情都应该用它们是什么来描述(例如,*数据处理、近似结果等),从历史的角度描述他们是如何实现的。Streaming究竟是什么,这在术语上缺乏精确的定义。在某些情况下,这给流式系统本身带来和负担,这意味着它们的能力仅限于流式系统经常被描述的那些特性,比如流式系统的结果都是近似的或推测的。 考虑到设计良好的流式系统能够产生正确、一致和可重复的结果,正如任何现有的批处理引擎一样。我更倾向于将Streaming这个术语定义为一个非常特定的含义: 一种设计时考虑到无限的数据集的数据处理引擎。仅此而已( 为了完整起见,也许有必要指出这个定义既包括真正的流实现,也包括微批处理实现)。

至于“Streaming”的其他常见用法,下面是我经常听到的一些词汇,每个词汇都有更精确的描述性,我建议我们作为一个社区应该尝试采用这些词汇:

Unbounded data: 一种不断增长的,本质上是无限的数据集。 这些数据通常被称为“流数据( streaming data.)”。 然而,对于术语Streaming或Batch等这些术语在描述数据集时是有问题的。因为如上所述,它们意味着使用某种类型的执行引擎来处理这些数据集。 实际上,这两种类型的数据集之间的关键区别在于它们的有限性,因此,最好使用能够捕捉这种区别的术语来描述它们。 因此,我将把无限的“Streaming”数据集称为Unbounded data(*数据),把有限的“batch”数据集称为bounded data(有界数据)。

Unbounded data processing: 应用于上述*数据类型的一种正在被使用的数据处理模式。虽然我个人很喜欢使用 Streaming 这个术语来描述这种类型的数据处理,但是在这种情况下使用Streaming也意味着使用流执行引擎,这更像是一种误导; 自从批处理系统最初被构想出来以来,就一直使用批处理引擎的重复运行来处理*数据( 相反,设计良好的流系统完全能够在有界数据上处理“ batch”类型的工作负载)。 因此,为了清晰起见,我将简单地将其称为*数据处理(Unbounded data processing)

Low-latency, approximate, and/or speculative results: 这些用来描述计算结果的词通常与流引擎( streaming engines)相关联, 传统上,批处理系统的设计并没有考虑到低延迟或推测结果,这是一个历史遗留问题,仅此而已。 当然,批量引擎完全能够产生近似的结果。 因此,与上面的术语一样,将计算结果描述为它们是什么( low-latency, approximate, and/or speculative)要比描述它们在历史上是如何表现的(通过流引擎)好得多。

从这里开始,每当我使用术语“Streaming”时,您都可以放心地认为我指的是为*数据集( unbounded data)设计的执行引擎,仅此而已,.当我说到上面的任何其他术语时,我会明确地说到*数据( unbounded data)、*数据( bounded data)处理或低延迟/近似/推测结果( low-latency / approximate / speculative results). 这些是我们在 Cloud Dataflow中采用的术语,我鼓励其他人采取类似的观点。

三、On the greatly exaggerated limitations of streaming

接下来,让我们讨论一下流式系统能做什么和不能做什么,重点是能做什么: 在这些帖子中,我想要表达的最重要的事情之一就是:一个设计良好的流式系统应该具有怎样的能力。 流式系统长期以来一直处于一个小众市场,提供低延迟、不准确/投机的结果,通常需要与更强大的批处理系统一起才能提供最终正确的结果,即Lambda体系结构( Lambda Architecture.)对于那些还不熟悉Lambda体系结构的人来说,其基本思想是在批处理系统的同时运行一个流式系统,这两个系统基本上执行相同的计算逻辑。 流系统提供了低延迟、不准确的结果( 要么是因为使用了近似算法,要么是因为流系统本身没有提供正确性保证)且一段时间之后,批处理系统开始运行,并提供正确的输出。这个架构最初由Twitter的Nathan Marz (Storm的创始人)提出,它最终非常成功,因为它在当时是一个非常棒的想法。 但是流式引擎在正确性方面有点令人失望,且正如您所预期的那样,批处理引擎天生就很笨拙,而Lambda提供了一种让您吃到蛋糕的方法。 不幸的是,维护Lambda系统是一个麻烦: 您需要构建、提供和维护 pipelines 的两个独立版本(batch layer 和 speed layer),然后在最后以某种方式合并来自两个 pipelines 的结果(serving layer)。

作为一个花了数年时间致力于强一致性流引擎的人,我还发现Lambda体系结构的整个原则有点令人讨厌。 我是Jay Kreps( Jay Kreps’ Questioning the Lambda Architecture)的追随者,当Lambda Architecture出现时同样对它存有质疑。Jay Kreps这篇文章是第一个反对双重模式执行必要性的文章;令人高兴的是, Kreps使用Kafka这样的可重复使用数据的系统作为流相互连接的桥梁,解决了流式系统可重复性的问题,甚至提出了Kappa体系结构,这意味着一个设计良好的系统仅需要使用运行一个管道,且这个系统是针对当前的工作而构建的。 我原则上完全支持这个概念。坦率地说, 更进一步,我认为设计良好的流系统实际上提供了严格的批处理功能的超集,即设计良好的流式系统完全可以执行批处理任务,所以现在应该不需要批处理系统了。值得称赞的是Flink的工作人员把这个想法放在了心上,并且建立了一个Flink系统,他是 all-streaming-all-the-time ,甚至是在“批处理”模式下。所有这一切的必然结果是:流系统的广泛成熟,加上用于无限数据处理的健壮框架,最终将使得Lambda体系架构成为大数据历史上的老古董。 我相信现在是实现这一目标的时候了,因为要做到这一点,即在Streaming自己的模式下击败Batch,你真的只需要两件事:

Correctness :在这方面Streaming可以做到与批处理一样的效果。 核心在于,正确性归结为一致的存储。流系统需要一种关于随着时间对checkpoint状态进行持久化的方法(Kreps在他的《Why local state is a fundamental primitive in stream processing》 中谈到了这一点),并且它必须经过良好的设计以在机器故障时保持一致。 当Sparkstreaming几年前首次出现在公共大数据领域时, 在一个原本黑暗的 streaming 世界里,它就像是一座灯塔,尤其是关于streaming 的一致性。 值得庆幸的是,从那以后情况有所改善,但值得注意的是,许多流系统仍然试图在没有强一致性的情况下生存;我真的不能相信 at-most-once processing仍然是一个问题,但事实上它确实仍是一个问题。 重申一下,因为这一点很重要: 对于exactly-once processing而言强一致性是必须的,这是正确性的必要条件,这是任何系统都必须具备的条件,它将有机会满足或超过批处理系统的能力,除非你真的不在乎结果, 我恳请您不要使用任何不能提供强一致性状态的流系统。 批处理系统不需要您提前验证它们是否能够生成正确的答案; 不要浪费你的时间在哪些不能满足同样标准的流系统上。 如果你想知道如何在流系统中获得强一致性,我建议你看下 MillWheel和 Spark Streaming 两篇论文都花了大量时间讨论一致性问题。 鉴于其他地方和很多文献中有关于这个主题的大量高质量的内容,我将不再在这篇文章中进一步介绍它。

Tools for reasoning about time :这方面Streaming将超越batch。 对于处理event-time skew不固定、*、无序的数据,好的时间推理工具是必不可少的。 越来越多的现代数据集显示了这些特性(event-time skew不固定、*、无序),而现有的批处理系统(以及大多数流系统)缺乏必要的工具来应对它们带来的困难, 我将用这篇文章的剩余部分和下一篇文章的大部分时间来解释和关注这一点。 首先,我们将对时域的重要概念有一个基本的了解,然后我们将更深入地了解我所说的*的(unbounded)、无序的(unordered)、 event-time skew不固定(varying event-time skew)的数据。 接下来,我们将使用批处理和流系统,研究有界和*数据处理的常用方法。

四、Event time vs. processing time

要有效地谈论*数据( unbounded data )处理,需要清楚地了解所涉及的时间域。在任何数据处理系统中,我们通常关心两个时间域:

事件时间(Event time): 这是事件实际发生的时间。

处理时间(Processing time): 流式系统中观察到事件的时间

不是所有用例都关心事件时间( event times )(如果你的用例不关心,万岁!你的生活更轻松),但很多人会。 例如随着时间的推移描述用户行为、大多数账单应用程序和许多类型的异常检测。

在理想情况下, event time 和 processing time总是相等的,即事件发生时立即进行处理。 然而,实际情况并非如此,event time 和 processing time之间的偏差不仅是非零的,而且通常是关于底层输入源、执行引擎和硬件特性的复杂的可变函数。同时影响因素包括:

1、共享资源限制,如非专用环境中的网络拥塞、网络分区或共享CPU

2、软件原因,如分布式系统逻辑、争用等

3、数据本身的特性,包括键分布、吞吐量的变化或无序方差(例如,一架飞机上的人,他们的手机在整个飞行过程中脱机使用后,都使用离线的飞行模式)

因此,如果在任何实际系统中绘制 event time 和 processing time 的进度,通常会得到类似于图1中的红线的结果

小白学习Flink系列--第二篇-01(流式数据概念)

图1:时域映射示例。X轴表示系统中 event time的完整性,即 event time中所有小于X的数据都被观测到。y轴表示 processing time的进度,即数据处理系统执行时观察到的正常时钟时间。

斜率为1的黑色虚线表示理想情况,其中 processing time和event time完全相等。 红线代表现实情况。在这个例子中,系统在 processing time的开始有一点延迟,在中间向理想的方向转变,然后再次延迟到最后一点。 理想值与红线之间的水平距离是processing time与event time之间的偏差,这种偏差实质上是pipeline处理引入的延迟。

由于processing time与event time之间的映射不是静态的,这意味着如果您关心数据的event time,那么您不能仅通过在pipeline中观察到的数据的上下文来分析数据。 不幸的是,现有的大多数系统针对*数据操作都是采用这种方式。 为了处理*数据集的无限特性,这些系统通常提供了一些数据窗口的概念。 下面我们将深入讨论窗口,但它实际上意味着沿着时间边界将数据集分割成有限的片段。

如果您关心正确性,且对在 event times上下文中分析数据感兴趣,那么您就不能像大多数现有系统所做的那样使用processing time(即,processing time windowing)来分析数据。 由于processing time与event time之间没有一致的相关性,一些event times数据最终会出现在错误的processing time窗口中( 例如,由于分布式系统固有的滞后,许多类型的输入源的在线/离线特性等原因) 就像把正确性扔出窗外一样。我们将在下面的一些例子以及下一篇文章中更详细地讨论这个问题。

不幸的是,按event times划分窗口时,情况也不是那么乐观。在*数据的背景下,无序和可变倾斜将导致event times的完整性问题: 在processing time与event time之间缺乏可预测的映射,对于给定事件时间点X,在pipeline中您如何确定何时观察到所有eventtime为X的所有数据? 对于许多真实世界的数据源,您无法确定,因为event time为X的所有数据可能会延迟到达pipeline(由于网络,或其他原因),且这个延迟时多久没有人知道。目前使用的绝大多数数据处理系统都依赖于某种完整性的概念,这使得它们在应用于*数据集时处于非常不利的地位。

我建议,与其试图将*数据整理为最终完整的有限批信息,不如设计一些工具,让我们生活在这些复杂数据集所带来的不确定性世界中:新数据会到达,旧数据可能会被收回或更新。我们构建的任何系统都应该能够自己处理这些事实,完整性的概念是一种方便的最优化的方案,而不是语义上的需要。

在深入研究我们如何使用 Cloud Dataflow模型构建这样的系统之前,让我们先完成一个更有用的背景:常见的数据处理模式:

五、Data processing patterns

现在,我们已经有了足够的背景知识,当前可以开始研究那些常用的核心的可以跨有界和*数据处理的模式。 我们将在我们关心的两种主要引擎的上下文中查看针对这两种类型数据的处理,以及与之相关的情况( 批处理和流处理,在这种情况下,我实际上是把微批处理和流处理混在一起,因为这两者之间的区别在这个级别上不是很重要)。

5.1、Bounded data

处理有界数据非常简单,可能每个人都很熟悉。 在下面的图表中,我们从左边开始,有一个充满熵(热力学的概念,可以简单的认为是比较杂乱的)的数据集。 我们通过一些数据处理引擎运行它( 典型的批处理,尽管一个设计良好的流引擎也能很好地工作),例如Mapreduce. 右边的是一个新的结构化数据集,且具有更大的内在价值:

小白学习Flink系列--第二篇-01(流式数据概念)

Figure 2: Bounded data processing with a classic batch engine.

左边的有限的非结构化数据池通过数据处理引擎运行,从而在右边生成相应的结构化数据。

当然,作为这个方案的一部分,你实际上可以计算的东西有无数的变化,但是整个模型非常简单。 更有趣的是处理*数据集的任务。 现在让我们看看通常处理*数据的各种方法:首先是传统批处理引擎使用的方法,然后是为*数据设计的系统(如大多数流媒体或微批处理引擎)常采用的方法。

5.2、Unbounded data — batch

批处理引擎虽然在设计时没有明确考虑到*数据,但自从第一次构思批处理系统以来,就被用于处理*数据集。 正如人们可能预期的那样,这种方法将*数据分割成适合批处理的有界数据集的集合。

5.2.1、Fixed windows

使用批处理引擎的重复运行来处理*数据集的最常见方法是将输入数据窗口设置为固定大小的窗口,然后将每个窗口作为独立的有界数据源来处理。 特别是对像日志类型的输入源,事件可以写入目录和文件,且目录或文件的名字编码与窗口相对应,这样事情乍一看似乎很简单,因为你实际上已经完成了基于时间的shuffer,数据已经被分配到它所属时间窗口对应的文件中。

然而,在现实中,大多数系统仍然有一个完整性问题需要处理: 如果出于网络分区的原因,您的一些事件在发送到日志文件的途中出现了延迟怎么办? 如果您的事件是全局收集的,并且必须在处理之前转移到一个公共位置,那该怎么办? 如果你的活动来自移动设备呢? 这意味着可能需要某种缓解措施( 例如,延迟处理直到您确定所有事件都已被收集,或者每当有延迟到达的数据进入窗口时,就重新处理给定窗口的整个批处理)

小白学习Flink系列--第二篇-01(流式数据概念)

Figure 3: Unbounded data processing via ad hoc fixed windows with a classic batch engine.

*数据集预先收集到有限的固定大小的有界数据窗口中,然后通过连续运行经典批处理引擎进行处理

5.2.2、Sessions

当您试图使用批处理引擎将*数据处理为更复杂的窗口策略(如 Sessions)时,这种方法就更容易失败。 Sessions通常定义为活动周期(例如,对于特定用户),以不活跃的间隙结束。 在使用典型的批处理引擎计算 Sessions时,Sessions通常会被不同的批次所分割,如下图中的红色标记所示。 通过增加批大小可以减少Session被分割的次数,但代价是增加延迟。 另一种选择是添加额外的逻辑,以便从之前的的运行结果中缝合会话,但代价是进一步增加复杂性。

小白学习Flink系列--第二篇-01(流式数据概念)

Figure 4: Unbounded data processing into sessions via ad hoc fixed windows with a classic batch engine.

*数据集预先收集到有限的固定大小的有界数据窗口中,然后通过连续运行经典批处理引擎将这些窗口细分为动态会话窗口。

无论哪种方式,使用传统的批处理引擎来计算会话都不太理想, 一个更好的方法是以流的方式构建会话,我们将在后面看到这一点。

5.3、Unbounded data — streaming

与大多数基于批处理的*数据处理方法相反,流系统是为*数据构建的。 正如我前面提到的,对于许多真实世界的分布式输入源,您不仅发现自己在处理*数据,而且还在处理以下数据:

Highly unordered with respect to event times, 这意味着,如果您希望在数据发生的上下文中(即基于event time时间域)分析数据,那么您的pipeline需要某种基于时间的shuffle Of varying event time skew :也就是说,在给定的event time X内,你无法在一定时间Y内看到大部分数据(或者说这个时间Y也是varying的)。

在处理具有这些特征的数据时,可以采用几种方法, 我通常将这些方法分为四类:

1、Time-agnostic

2、Approximation

3、Windowing by processing time

4、Windowing by event time

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

5.3.1、Time-agnostic

Time-agnostic processing在以下情况中使用: 时间是无关紧要的,所有相关逻辑都是数据驱动的。 由于这种情况下所有任务内容都是由更多数据的到来决定的,所以流引擎除了基本的数据传递之外,实际上没有什么特别的任务需要处理。 因此,基本上所有现有的流系统都支持开箱即用的与时间无关的场景( 当然,对于那些关心正确性的人来说, system-to-system的变动具有一致的担保)。 批处理系统也非常适合于对*数据源进行时间无关的处理,只需将*数据源分割为任意的有界数据集序列,并独立地处理这些数据集。 在本节中,我们将会看到几个具体的例子,但是考虑到对于时间无关数据处理的简单性,在此之后,我们不会在这上面花费更多的时间。

(1)Filtering

时间无关处理的一种基本形式是过滤, 假设您正在处理Web流量日志,您只需要特定域名下的流量,其他域名下的流量数据过滤掉, 你可以在每个数据记录到达时查看它,看看它是否属于自己所需要的域名,如果不属于,则将其删除。 对于这类处理任务,在任何时候都只依赖于单个元素,所以数据源是*的、无序的和具有 varying event time skew 的事实与过滤本身是无关的。

小白学习Flink系列--第二篇-01(流式数据概念)

Figure 5: Filtering unbounded data

包含不同类型的数据集合(从左到右流动)被过滤为 包含单一类型的同构集合

(2)Inner-joins

另一个与时间无关的例子是内连接(或hashjoin)。 当连接两个*数据源时,如果您只关心来自两个数据源的元素到达时连接的结果,即逻辑上就不存在时间元素。 当从一个源中看到一个值时,您可以简单地对其状态进行缓冲存储;当第二个来自另一个源的值到达时,您只需要输出连接后的记录( 实际上,您可能希望针对未成功进行JOIN连接的数据使用某种垃圾收集策略,这种策略可能是基于时间的。但是对于不存在或很少的没有完成JOIN的数据的情况,这样的事情可能不是问题)

小白学习Flink系列--第二篇-01(流式数据概念)

Figure 6: Performing an inner join on unbounded data.

当观察到来自两个源的匹配元素时,就会产生连接

切换到某种外部连接的语义时,将引入我们讨论过的数据完整性问题: 一旦你看到了连接的一边,你怎么知道另一边是否会到达? 说实话,你不知道。 所以你必须引入“超时”的概念,它引入了时间元素, 这个时间元素本质上是窗口的一种形式,我们稍后会更仔细地研究它。

5.3.2、Approximation algorithms

小白学习Flink系列--第二篇-01(流式数据概念)

Figure 7: Computing approximations on unbounded data.

数据通过一种复杂的算法运行,生成的输出数据或多或少与另一端期望的结果相似

第二种主要的方法是近似算法,例如 approximate Top-N, streaming K-means, 等等。 它们获取无限的输入源,并提供输出数据,如果你眯着眼看,这些数据或多或少有点像你希望得到的结果。 近似算法的优点是,从设计上来说,它们的开销很低,而且是为*数据设计的。 缺点是它们的数量有限,算法本身往往很复杂( 这使得召唤新的元素变得很困难) 它们的近似性质限制了它们的实用性。

值得注意的是:这些算法在设计中确实有一些时间元素( 某种固有的衰变), 由于它们在数据到达pipeline时就开始处理数据,所以时间元素通常是基于processing time的。 对于那些在近似上提供某种可证明误差界限的算法来说,这一点尤为重要。 如果这些错误界限是基于按顺序到达的数据来预测的,那么当您使用 varying event-time skew的无序数据来训练算法时,它们实际上没有任何意义。这需要谨记。

近似算法本身是一个很吸引人的主题,但因为它们本质上是时间无关处理的另一个例子( 调制算法本身的时间特性), 它们使用起来非常简单,因此在我们目前关注的问题上不值得进一步关注。

5.3.3、Windowing

其余两种处理*数据的方法都是窗口的变体。 在深入研究它们之间的差异之前,我应该明确说明我所说的窗口化是什么意思,因为我只简单地提到过它。 窗口就是从数据源获取数据(要么*,要么有界) 然后沿着时间边界把它切成有限的块进行处理。 下图显示了三种不同的窗口模式:

小白学习Flink系列--第二篇-01(流式数据概念)

Figure 8: Example windowing strategies

每个示例显示三个不同的键,突出显示对齐窗口(应用于所有数据)和未对齐窗口(应用于数据子集)之间的区别

Fixed windows:固定窗口就是把时间切分成具有固定时间长度的时间片段,通常(正如图8所示),固定窗口的时间段被应用于整个数据集,这就是对齐窗口( aligned windows)的例子。在某些情况下,最好对不同的数据子集( (e.g., per key))进行 phase-shift the windows,以便随着时间的推移更均匀分配窗口负载, 相反,这是一个未对齐窗口的例子,因为它们在数据上是不同的。

**Sliding windows:**是固定窗口的一种推广,滑动窗口由固定长度和固定周期定义,如果周期小于长度,那么窗口重叠。 如果周期等于长度,就有固定的窗口。 如果周期大于长度,就会有一个奇怪的抽样窗口,它只查看随时间变化的数据子集。 与固定窗口一样,滑动窗口通常是对齐的,尽管在某些用例中出于性能优化的考虑,它可能是不对齐的。 注意,图8中的滑动窗口是为了给人一种滑动运动的感觉而绘制的; 实际上,所有五个窗口都适用于整个数据集。

Sessions: 一个滑动窗口的例子, 会话由一系列事件组成,这些事件的终止时间间隔大于某些超时时间, 会话通常用于分析用户在一段时间内的行为,方法是将一系列与时间相关的事件组合在一起( 例如,一次观看的一系列视频)。 会话很有趣,因为它们的长度不能预先定义, 它们依赖于所涉及的实际数据。 它们也是未对齐窗口的典型例子,因为在不同的数据子集之间,会话实际上从来不是相同的( 例如,不同的用户)。

讨论的两个时间域——processing time与event time——本质上是我们关心的[2]。 在这两个领域中,窗口都是有意义的,因此我们将详细研究每个时间域,并了解它们之间的区别。 由于processing time窗口在现有系统中更为常见,我将从这里开始。

(1)Windowing by processing time

小白学习Flink系列--第二篇-01(流式数据概念)

Figure 9: Windowing into fixed windows by processing time.

数据根据到达pipeline的顺序被收集到窗口中

当通过processing time划分窗口时,系统基本上会将传入的数据缓冲到窗口中,直到经过了一定的processing time。 例如,在窗口长度为5分钟的固定窗口的情况下,系统将为5分钟的processing time缓冲数据, 在此之后,它将把在这五分钟内观察到的所有数据当作一个窗口,发送到下游进行处理。 processing time窗口有几个很好的特性:

这很简单。这个实现非常简单,因为您从来不用随着时间的推移对数据进行shuffle(不用关心迟到的数据)。你只需要在它们到达时进行缓冲,并在窗口关闭时将它们发送到下游。

如果你想要根据观察到的数据来推断某些信息,processing time窗口正式你所需要的, 许多监控场景都属于这一类。 想象一下,跟踪发送到全球范围的Web服务的每秒请求数。 为了检测中断而计算这些请求的速率是processing time窗口一种完美的使用场景。

撇开好的方面不谈,processing time窗口有一个非常大的缺点, 如果相关数据具有event time,那么如果processing time窗口要反映这些事件实际发生的时间,那么这些数据必须按照event time顺序到达。 不幸的是,在许多实际的分布式输入源中event time有序的数据并不常见。

举个简单的例子,想象一下任何一个移动应用程序,它收集统计数据以供以后处理。 如果给定的移动设备在任何一段时间内都可能处于离线状态(短暂的连接中断,飞越国家时的飞行模式,等等), 在此期间记录的数据将不会被上传,直到设备再次上线。 这意味着数据可能以分钟、小时、天、周或更长时间的 event time skew到达pipeline。在使用processing time划分窗口时,基本上不可能从这样的数据集中得出任何有用的推论。 另一个例子是,当整个系统运行良好时,许多分布式输入源似乎会提供event time有序的数据(或者几乎有序). 不幸的是,当输入源处于健康状态时,event time skew很低,这并不意味着它将始终保持这种状态。 考虑一个全局服务的例子,它需要处理从多个大洲收集的数据。 如果网络在带宽受限的跨大陆线路上出现问题(遗憾的是,这种情况非常普遍)这些网络问题会进一步降低带宽和/或增加延迟,那么您的输入数据中有一部分数据可能会出现比以前更大的skew。 如果通过processing time对这些数据进行窗口化处理,那个该窗口不再代表其所对应时间段内实际发生的数据; 相反,这是事件到达处理pipeline时的时间窗口,其中的数据是旧数据和当前数据的任意组合。

在这两种情况下,我们真正想要的是通过event time来划分窗口数据,这种方式对事件到达的顺序是可以保证的, 即我们真正想要的是event time窗口。

(2)Windowing by event time

event time窗口是在需要以有限个时间块来观察数据源时使用的窗口,这些数据源反映了这些事件实际发生的时间。 这是划分窗口的黄金标准。 遗憾的是,目前使用的大多数数据处理系统缺乏对它的良好的支持( 尽管任何具有良好一致性模型的系统,如Hadoop或SparkStreaming,都可以作为构建这样一个窗口系统的合理基础)

这张图显示了一个将无限源按照长度为一小时的固定窗进行窗口划分的例子:

小白学习Flink系列--第二篇-01(流式数据概念)

Figure 10: Windowing into fixed windows by event time.

数据被收集到窗口中根据数据被pipeline发现的时间。白色箭头显示processing time窗口中到达的示例数据,这些数据与它们所属的event time窗口不同

图中的白色实线显示了两个特定的数据。 这两个数据都是在processing time窗口中到达的,它们所在的processing time窗口与它们所属的event time窗口不匹配。 因此,如果这些数据被窗口化到processing time窗口中,而processing time窗口又与event time有关,那么计算出来的结果就不正确了。因此使用event time的一个好处就是,保证event time的正确性。

event time窗口在*数据源上的另一个好处是,您可以创建动态大小的窗口,例如session, 没有像之前那种,在固定窗口上生成session时所产生的对session的任意分割(正如前面session例子中所看到的)。

小白学习Flink系列--第二篇-01(流式数据概念)

Figure 11: Windowing into session windows by event time.

数据被收集到会话窗口,根据相应事件发生的时间捕获活动的突发事件。白色箭头再次指出将数据放入正确的event time位置所需的时间shuffer。

当然,强大的语义很少免费提供(是有代价的),event time窗口也不例外。 因为窗口的生存时间(在处理过程中)通常比窗口本身的实际长度长(因为会有迟到的数据),event time 窗口有两个明显的缺点,:

Buffering: 由于窗口寿命的延长,需要更多的数据缓冲。 值得庆幸的是,持久存储通常是大多数数据处理系统所依赖的资源类型中最便宜的一种( 其他的主要是CPU、网络带宽和RAM)。 因此,在使用任何设计良好的数据处理系统(具有强一致的持久状态和良好的内存缓存层)时,这个问题通常不像人们想象的那么严重, 此外,许多有用的聚合不需要缓冲整个输入集(例如,sum或average),而是可以使用存储在持久化状态中的更小的中间聚合数据增量地执行。

Completeness: 由于我们通常无法很好地知道什么时候看到了给定窗口的所有数据,那么我们如何知道窗口的结果什么时候可以物化呢?事实上,我们无法知道。 对于许多类型的输入,系统可以通过诸如MillWheel watermarks (我将在第2部分中详细讨论)之类的东西给出一个相当精确的启发式窗口进行估计。 但在要求数据绝对正确情况下(例如,计费),唯一真正的选择是为管道构建器提供一种方法,以便在pipeline builder想要物化windows的结果时表达这些结果,以及如何随着时间的推移对这些结果进行细化。 处理窗口完整性(或缺少窗口完整性)是一个很吸引人的主题,但在具体的示例中(我们将在下一节中讨论)可能会得到最好的探讨。

六、Conclusion

Whew! 这是很多信息。你们中间有走到这地步的,当受人称赞。 在这一点上,我们已经大致完成了我想要介绍的内容的一半,因此,退一步,重述我到目前为止所讲的内容,并让事情在进入第2部分之前稍作休息是合理的。 这一切的好处是,第1部分是枯燥的内容;第2部分是乐趣真正开始的地方。

七、Recap

明确的术语,明确地缩小了“Streaming”的定义,例如:只适用于执行引擎,同时使用更多的描述性术语,如*数据( unbounded data)和近似/推测的结果( approximate/speculative results),这些不同的概念通常被归类在“Streaming”的范畴

评估了设计良好的批处理和流系统的相对功能,假设流处理实际上是批处理的超集(即流处理系统完全可以胜任所有批处理任务),以及Lambda体系结构(基于流处理不如批处理的假设)等概念,随着流处理系统的成熟,这些概念注定要退役。

提出了Streaming系统追赶并最终超越批处理所必需的两个高级概念, 它们分别是正确性( correctness)和时间推理的工具( tools for reasoning about time)。

建立event time与processing time的重要区别, 描述这些差异在分析数据时所带来的困难, 并提出了一种方法上的转变,从完整性的概念转变为简单地适应数据随时间的变化。

通过批处理和流引擎,研究了当今常用的有界和*数据的主要数据处理方法, 将*方法大致分类: time-agnostic, approximation, windowing by processing time, and windowing by event time.

八、Next time

本文为我将在第2部分中探讨的具体示例提供了必要的上下文。文章中大致包括如下内容:

从概念上看,我们是如何分解数据流模型中数据处理的概念的: what, where, when, and how.

详细介绍如何跨多个场景处理简单、具体的示例数据集, 突出 Dataflow Model支持的多个用例,以及所涉及的具体api。 这些示例将帮助您理解本文中介绍的event time与processing time的概念,同时还将探索新的概念,如 watermarks.

比较现有的数据处理系统的重要特征,这些特征的在这两个文章中都有提及,以便于更好的在这些数据系统中进行选择,并鼓励对不足的地方进行改进,我的终极目标是改善数据处理系统,特别是整个大数据社区中的流式系统,。

型中数据处理的概念的: what, where, when, and how.

详细介绍如何跨多个场景处理简单、具体的示例数据集, 突出 Dataflow Model支持的多个用例,以及所涉及的具体api。 这些示例将帮助您理解本文中介绍的event time与processing time的概念,同时还将探索新的概念,如 watermarks.

比较现有的数据处理系统的重要特征,这些特征的在这两个文章中都有提及,以便于更好的在这些数据系统中进行选择,并鼓励对不足的地方进行改进,我的终极目标是改善数据处理系统,特别是整个大数据社区中的流式系统,。

应该是一段美好的时光。到时候见!