近年来,在大数据场景下,流式的数据处理因其自身的原因已经变得愈发重要。主要以下几点原因:
1. 业务极度渴望获取更及时的数据洞察力,而切换到流式处理是一个达成低延迟的一个很好的方法。
2. 在现代商业中,更庞大地、无界的数据集愈发普遍。如此庞大的数据集其实更容易被针对永不停止的数据量的系统设计所控制。
3. 数据的处理会随着时间的推移变得更加平滑,并且产生更加一致且可预期的资源消耗。
尽管这股流式计算的浪潮是由于业务驱动的,导致与批处理相比,流式系统长时间都相对不成熟。但直到最近,这股浪潮最终摇摆至另外一个方向。我有时自以为是的认为,可能是因为我最初所提供《Streaming 101》和《Streaming 102》博客起到了一些作用(可以很明显的看出本书的最初几个章节就是来自于此)。但实际上,有大量的工业界对流式系统的成熟感兴趣,大量聪明和活跃的同僚们一同参与且构筑它。
虽然在我看来这场关于流式系统的提倡已经赢了,但我仍要阐明之前在《Streaming 101》中阐述过的,这看法其实本质上没怎么变过。原因有二。其一,时至今日,流式计算十分具有可用性。即使许多工业界的人已经注意到这场争辩的呼声。其二,仍有很多同僚对此还不太了解,这本书就是尝试对一些观点做延伸。
作为开始,我先总结出一些重要的背景知识。这将会把我想要讨论的一些主题框定起来。主要包含以下三个特定的章节:
术语
为了精确的讨论这些复杂的主题,需要对词汇做一些限定。我将会尽量的阐明我所表达的词汇所对应的含义,会在现有的使用场景下对一些词汇做过多的解释。
特点
我会指出流式系统中常被人诟病的缺。我也会提出一个心态,我相信数据处理的构建者们需要适应为了满足未来数据消费者的需求
时域
我会介绍与数据处理相关的时域的两个要素,展示他们如何相关,指出这两类时域所暴露出来的一些困难。
术语:什么是流
在开始之前,我想先讨论一个问题:究竟什么是流?时至今日,流一词可理解为大量不同的事物(为了使用方便,我使用时相当的宽松),但这导致了对流以及流式系统究竟是什么被误解。因此,我想先精确的定义下这些词汇。
问题的关键在于很多事情应该用是“什么”来描述(无界的数据处理,近似的结果等),但已经被口语化的描述怎么来达成这个目标(例如通过流式计算引擎 )。这种缺乏精确度的术语描述使得流究竟是什么变得模糊,并且在大多数场景下它局限了流式系统所原本具有的含义,使得它的功能仅限于所描述的“流”,如近似或推测的结果。
与一个已经存在的批处理引擎相比,一个经过好的设计的流式系统同样有能力能产生正确的、一致的和可重复的结果,因此我更倾向于脱离“流”这个概念去给出一个特定的定义:
流式系统
一种被设计用来处理有着无限数据集的数据处理引擎
如果我想谈及低延迟、近似或推测的结果,我会使用这些特定的词汇而不是简单的称之为“流”
精确的词当碰到可能遇到需要讨论不同数据类型时是有用的。从我的观点来看,有两个衡量给定数据集形态的重要维度,即基数和构成。
数据集的基数描述了它的大小,以及基数最重要的在于描述了一个给定数据集是有限还是无限的。我想用这两个词汇来描述数据集中的粗糙基数:
有界数据
一种大小确定的数据集
无界数据
一种大小是不确定的数据集(至少理论上是无限的)
基数很重要,因为无限数据集的无界特性使得数据处理框架在消费它时带来了额外的负担。更多的内容我们会在下一节讨论。
另一方面,数据集的构成,规定了它的物理表示。因此,构成 规定了相关数据交互的方式。我们将会在第6章更深入的讨论构成,但会先给出摘要,有两类重要构成。
表
数据集在某个特定时间点的整体视图。SQL一般就是用于处理表。
流
指在随时间演变的数据集的一个接着一个的元素视图。基于MapReduce血缘关系的数据处理系统一般用来处理流。
我们将会在第6、第8和第9章深入探究流和表的关系,并且在第8章我们也会把时变关系中的一些基本概念打包学习下。但当前我们只关心数据流就好了,因为这是数据管道的开发人员在大多数数据处理系统接触到的最直接的概念(包括批处理和流式处理),而这也是最能提现流式处理所特有的挑战的地方。
流式中被夸大的限制
说到这,我们将会讨论下流式系统能做什么以及不能做什么,并且重点强调能做什么。其中我在这一章节重点讨论的是一个经由良好设计的流式系统的能力。流式系统在历史上的演变和提供低延迟、不准确的和推测的结果再加上一个能提供精确结果的批处理系统有关,换个表达方式,其实也就是和Lambda架构有关。
针对那些可能对Lambda架构并不熟悉的人来说,一个基本的概念就是你执行一个流式系统的同事还有一个批处理系统,两者都使用一样的计算方式。其中流式系统提供了低延迟、非准确的结果(可能是因为使用了近似计算,或者流式系统本身就不提供正确性),然后一段时间后批处理系统逐步的提供准确的输出。这个架构最初是由Twitter的Nathan Marz(由Storm首创)提出,这个架构想的那个的成功,而且实际上在当时这算一个绝妙的办法。流式引擎在正确性上让人略显失望,批处理引擎和你预想的一样天生略显笨重,因此Lambda架构能带来能预期的成果。不幸的是,维护一个Lambda架构非常复杂。你需要构建、规定以及维护两个独立版本的数据管道并且知道怎么最终合并两个数据管道的结果。
作为一个在强一致的流式处理引擎上工作多年的人,我发现在完整的Lambda架构不那么尽如人意的地方。毫无疑问ia,当Jay Kreps发表了《Questioning the Lambda Architecture》的时候我也是他忠实的粉丝。下面是第一个非常明显的观点来反对这种双模式执行的必要性。Kreps阐明一个观点,在一个可重放的环境中,例如使用一个类似Kafka的可重放系统作为流式的连接点,以至于提出了Kappa架构,大概意思就是使用一个设计良好的单一数据管道来适当满足手头工作的要求。我并不完全同意使用希腊字母来命名,但我原则上支持这一架构。
更进一步的,我会论证一个设计良好的流式系统实际上在功能上是批处理的一个超集。Modulo perhaps an efficiency delta,批处理系统时至今日可能都没有存在的必要了。并且Apache Flink认真吸取了这一想法并且构建了一个即使在“批处理”模式下也是完全流式的系统。我非常喜欢它。
批处理和流式处理的效率差异
其中我想提的有一点不是流式系统的固有限制,而是目前在设计选型时选择流式系统的主要原因。批处理相较于流式处理的增益很大程度上是由于增加了打包以及更有效的shuffle传输。现代的批处理费了很大的力气去实现复杂的优化使得它允许使用有限的计算机资源把吞吐量提升到一个显著的水平。没理由不能将批系统纳入到针对无界数据的系统设计中,以提供用户在我们主要考虑的高延迟、高效的批处理以及低延迟和低有效的流式处理之间作灵活的选择。我们在Google所做的Cloud Dataflow,它提供了在相同的统一模型下的批处理和流式处理两种运行方式。在我们的场景中,我们使用隔离的运行器,因为我们可以做到针对两种独立的设计系统为他们特定的使用场景作优化。长久以来,从工程师的视角来看,我很乐于看到我们合并了两种模式到一个单独的系统中,将两者好的地方予以保留而又不失选择适当效率级别的灵活性。但我们目前还没做到这一点。诚恳地讲,感谢统一的Dataflow模型,如果没有它,可能这一切都不复存在。
这一切导致的必然结果就是广泛成熟的流式系统和针对无界数据处理的鲁棒性框架相结合,并完全将Lambda架构降至它所属的大数据远古时期。我相信随着时间这一趋势将变为事实。因为这样做,也就是说替换掉批处理,你其实只需要作两件事情。
正确性
这让你与批处理等价。核心在于,正确性问题可以归结为一致性的存储。流式系统需要一种能将持久化状态的快照化的方法(也即Kreps之前提过的《为什么本地化状态是流式处理的基础要素?》),并且它必须设计的足够好以保证机器故障时的一致性。当Spark Streaming在几年前第一次出现在大数据场景的时候,它是处于摸索阶段的流式世界中标志性的一致性。幸亏事态有所改善,但需要注意的是仍有很多流式系统仍在未提供强一致的境地下过活。
为了重申这点的重要性:强一致性是有且只有一次正确处理的必备条件,同时强一致也是对于任一系统来说能与批处理系统齐平或者超越批处理西欧她那个的必要条件。除非你并不真正关注你的结果,不然的话我恳请你避开使用所有的未提供强一致性的流式系统。一些批处理系统并不要求你提前验证,如果它有提供产生正确答案的能力;不要把你的时间浪费在那些没有提供强一致的流式系统上。
如果你因好奇想学习更多关于在流式系统中需要什么才能实现强一致的话,我推荐你看下MillWheel、Spark Streaming和Flink快照的论文。这三篇均花费了大量的篇幅来讨论一致性。Reuven将会在第五章深入解读一致性保障,如果你自己想要了解更多,那有数量庞大的知识是关于这个主题的。
时间推理工具
这可以让你超越批处理。好的时间推理工具对处理无界无序的不同事件时间倾斜是最基本的。数量递增的现代数据集展示了这方面的特征,而已存在的批处理系统(包括许多流式系统)缺乏此类必要的工具去处理它们所带来的困难(虽然这一现状快速转变,就在我写此篇时)。我们将话费这本书大部分篇幅去解释以及聚焦在不同方面的要点上。
作为开始,我们对时域方面的重要概念有一个基本的理解了,在我们深入探究我所说的无界无序数据的可变事件时间倾斜之后。我们会在本章剩余的篇幅中看一下在批系统和流式系统中对于处理有界和无界数据的共通方法,
事件事件 VS 处理事件
为了有说服力的讲解无界数据处理需要先对时域有一个清晰的理解。在任何的数据处理系统中,有两种我们比较关心的时域类型
事件时间
即事件发生的时间
处理时间
事件在系统中被观察到的时间
不是所有的使用场景都会用到事件时间(如果你没用到的话,好极了,你的生活更简单),但是很多会。举几个例子来说,有描述用户随时间变化的行为,多数的计费应用,和多种异常检测的类型。
在一个理想世界中,事件时间和处理时间总是相等的,也就是说事件总是在他们发生时候立即被处理掉。真实情况可没有这么有好,而且两者之间的偏离不止是零,而经常是一个与潜在的输入源,执行引擎和硬件高度变化的函数。能影响偏离级别的因素包含如下:
* 共享的资源限制,如网络阻塞,网络分区,或非专用环境下的CPU共享
* 软件所引起的偏离。如分布式系统逻辑等
* 数据他们自身的原因,如按key的分发,变化的吞吐量,或者无序的变化(如一架飞机上所有的人在整个航程中手机使用离线模式之后脱离了飞行模式)
因此,如果你绘制出在任何现实世界系统中事件时间和处理时间的进度的话,你最终会得到图1-1中红线部分类似的东西。
图1-1 时域对应关系。x轴代表在系统中完整的事件时间。。。。
在图1-1中,黑色虚线代表理想情况,处理时间和事件时间完全相等。红色线代表现实情况,在这个例子中,系统最开始处理时间稍微有些延迟,然后突然在中间部分大概贴近事件时间,然后最终重新有了点延迟。乍一看,在表格中有两种类型的时间偏离,分别是在两种时域内。
处理时间
理想情况与红色线之间的垂直距离是处理时间上的滞后。这个距离意味着在给定的事件发生的时间和它们被处理的时间之间的延迟情况。这种是更自然和直观的两个偏离。
事件时间
理想情况与红色线之间的水平距离是指在某一时刻在管道中的事件时间偏移量。它告诉我们当前在管道中具体理想情况差距有多远。
实际中,处理时间延迟和事件时间偏差在任一给定的时间点是等价的。它们只是看到相同事物的两种方式。延迟还是偏差的要点是:因为整体上事件时间与处理时间之间的对应关系不是静态的(例如延迟/偏差随时间的变化而任意变化),这就意味着如果你关心它们的事件时间(例如就是事件发生的时间)你就不能在你于管道中所观察到的上下文中去单独的分析数据。不幸的是,这种方式就是历史上许多对于无界数据的系统设计。为了处理无界数据的无限性,这些系统基本提供了一些针对将要到来的数据的窗口操作。我们将会在之后深入讨论窗口操作,但它本质上意味着要将数据集按照现实时间切割为多个有限的片段。如果你关心正确性以及对分析它们的事件时间上下文感兴趣,你就不能用处理时间来定义它的时间边界(例如处理时间的窗口),但很多系统这样做;处理时间和事件时间之间如果没有什么持续相关性的话,一些基于事件时间的数据会落到错误的处理时间窗口中(由于在分布式系统中不固定的延迟,线上和离线多种输入源的性质等),将很多正确的数据抛到窗口外面。我们会通过下面的章节以及本书剩余篇幅中的一些示例更详细阐述这个问题。
不幸的是,当按照事件时间进行窗口化时图并不如想象的那么美好。在无界数据的场景中,无序和变化的偏差会给事件时间的窗口化带来完备性问题:缺乏可预期的事件时间和处理时间的对应关系,对于给定的事件时间X你怎么来决定何时你能观察出来所有数据呢?对于现实时间的数据源,你其实是做不到的。但现今使用的大多数不同的数据处理系统依赖一个完备性的观点,这使得它们应用于无界数据集时处于严重的劣势。
我提出用试图整理无界数据的方法来代替最终完备的有限批次消息,我们应该设计一套工具来允许我们生活在复杂数据集所带来的不确定性的世界中。新的数据将会到来,老的数据可能会撤回或更新,我们所构建的系统将都会自行的正确处理,如果有一些完备性的定义,在特定和一些适当的用例中将便于做优化,而不是自始至终要求的一种强制性的语义。
在详细介绍这种方法可能是什么样子之前,我们用一个更有用的背景知识作为结束------数据处理的公有模式。
数据处理的范式
在这一节中,我们已经有了足够的公认的背景,这使得今天我们能开始看下从有界到无界数据处理中几个核心的使用范式类型。我们来看下两种类型的处理和它们之间的相关性,基于我们所关注的两种主要的引擎类型(批式和流式,我基本上把微批次归结为流式,因为两者之间的区别在这一层级并没有显著的区别)。
有界数据
有界数据的处理可能大家都很熟悉,它在概念上非常直接。在图1-2中,我们从左边看到的是一个充满无序数据的数据集,我们通常通过一些数据处理引擎来处理它(典型的有批式的,通过设计良好的流式引擎会起到一样的作用),例如MapReduce,从右边呈现出最终变成了新的更具价值的结构化的数据集。
图图图图图
图1-2 。。。。。。
虽然对于你能进行的实际计算有无限种变化,但总的来说模型是非常简单的。处理无界数据集的任务其实是更有趣的。我们现在来看下无界数据通常处理的不同方式,首先会介绍传统的批式引擎的方法,然后最后会介绍下你能基于为无界数据设计的系统的方法,例如大多数流式引擎或微批次引擎。
无界数据:批处理
固定窗口
用可重复运行的批式引擎处理无界数据集的最通用方法是将输入的数据窗口化成固定大小的窗口,然后将这些窗口作为分离的、有界的数据源(有时也叫作滚动窗口),如图1-3所示。尤其像类似于日志的输入源,这类时间能够写入像文件夹和文件这类层次结构中,并且按照它们的名字来命名窗口,这类解决办法乍一看看上去非常直接,因为你基本是提前按照时间做shuffle去执行,使得让数据分散到合适的事件时间窗口。
然而实际上,大多数系统仍需解决完备性相关的问题(如果你的一些事件因为网络分区导致在途中延迟到达怎么办?如果事件被全局收集起来然后在处理之前必须转移到一个公有的位置上怎么办?如果事件来自移动设备呢?),这就意味着某种缓解措施可能是必须的(例如延迟到你确定所有的事件被收集了或者当数据延迟到达时重新处理给定的窗口所对应的整个批次)。
图图图图图图
图1-3。。。。。。。
会话
当你想用批处理引擎将无界数据处理成更复杂的窗口策略,如会话的时候,这种方法更遭。会话通常可以被定义成周期性活动(如特定的用户)并且由非活跃期终止。当使用一般的批式引擎计算会话时,你会经常将会话最终分割成跨多个批次,如图1-4中红色标记的部分。我们可以通过增加批次大小的方式来减少分割的情况,但这增加了延迟。另外的方法是需要增加额外的逻辑来缝合之前跑过的会员,这也增加了更多的复杂性。
图图图图图图
图1-4.。。。。。。
不论用哪种方法,使用经典的批式引擎来计算会话都不太理想。更好的方法是以流式的方式构建一个会话,稍候我们会看到。
无界数据:流处理
与基于批次的方式来处理无界数据那些临时性质相反的是,流式系统就是为了无界数据而生的。如我们之前谈及的那样,对于许多真实世界的场景,分布式的输入源,你不仅能找到你自己对于无界数据的处理方式,还能处理如下的情况:
事件时间乱序的,这就意味着如果你想在这种情况下分析数据就必须在你的管道中基于时间做某种shuffle。
事件时间偏差有变化的,这就意味着你不能做如下假定,对于给定的事件时间X在时间Y的某个常数范围内,总能看到大多数数据。
当你要处理有这些特性的数据时,有小撮方案可供你选择。我通常将这些方法分为四组:时间不可知,近似值,按处理时间窗口化,按事件时间窗口化。
时间不可知
时间不可知主要用于时间无关的场景,主要是,所有相关的逻辑都是数据驱动的。因为这类场景的所有情况都是被更多到来的数据驱动的,实在是不需要流式引擎特别处理,除了基本的数据传递。因此,基本所有已存在的流式系统对于时间不可知的场景都是开箱即用(当然,如果你关注正确性的话,会存在一致性保障的系统差异)。批式系统也能很好的适配时间不可知类型的无界数据源的处理,这是通过将无界数据源切割成任意顺序的有界数据集,然后再单独处理这些数据集。我们会在这一节看一些具体的例子,但考虑到时间不可知处理的直接性,我们不会更深入的讨论这些例子。
过滤:时间不可知类的处理最基本的形式就是过滤了,如图1-5的例子所示。摄像下你在处理网络流量日志,然后你想从所有的流量中过滤出源于特定范围的流量。你想看下它到来的每条记录,看下它是否属于感兴趣的范围,如果不是就丢掉。因为这类事情在任意时间仅依赖于单个元素,实际上这也与数据源的无界性、无序性和事件时间的偏移都是无关的。
图图图图图图图
图1-5
内连接:另外一个时间不可知的例子是内连接,图解如图1-6。当两个无界数据源想连接,如果你只关注从两个数据源到来的元素连接的结果,而且没有其他时间元素的逻辑。一看到一个数据源的某条记录,你可以简单的将其缓存到持久化状态中,只有从另外一个数据源中来了你需要的第二条记录之后才会发送连接上的记录(事实上,你可能想要某种垃圾回收策略来处理未发送的部分连接,这可能是基于时间的,但对于没多少或没有不完整的使用场景来说,这类事情可能不是问题)。
图图图图图图
图1-6.。。
切换到某些外连接的语义来介绍下我们想要讨论的数据完备性问题,当你看到连接一边的数据后,你怎么知道另一边是已经来了还是没有呢?实话实说,你不能知道,所以你需要引入超时的概念,这就需要引入时间。这个时间基本就是以窗口的形式出现,我们马上就会看到。
近似算法
第二类主要方法就是近似算法,例如近似的 Top-N,流式的k-means等等。他们接入无界的输入源然后提供你大概看看的输出结果,看起来或多或少像是你想要得到的那样,就像图1-7那样。近似算法好的一面是,通过设计他们是低开销的并且为无界数据设计。糟糕的一面是这类算法有限,而且算法本身一般比较复杂(这使得它们很难创造出新的),而且他们的近似特性限制了他们的实用性。
图图图图图图图图
图1-7
这些算法中在他们的设计中通常加入时间这一元素是不值得的(例如某种内在的衰减)。并且因为他们处理他们到来的元素,通常时间也就用处理时间了。算法能提供他们近似时某种可证明的误差范围是非常重要的。如果在数据是有序到达时误差范围是可控的,当供算法使用的是变化的事件时间偏差的无序数据时是毫无意义的。有些事情请牢记在心。
近似算法本身是极具吸引力的课题,但他们基本算是时间不可知处理的另一个例子(算法本身的时间特征),他们用起来比较直接并因考虑到现状不值得进一步关注。
窗口化
剩下的两个处理无界数据的方法都是窗口的不同变化了。在深入研究它们之前,我需要说清楚我所表达的窗口是什么,就当前的程度我们涉及到的窗口仅简要的在前一节提及过。窗口化可简单的概括为从数据源(无界或有界)开始,沿着现实时间进行切割成有限的块,然后再处理。图1-8呈现了三种不同的窗口模型。
图图图图
图1-8图图图
让我们更进一步看下每个策略
固定窗口(又叫滚动窗口)
我们在之前讨论过固定窗口,固定窗口将时间划分成多个固定时间长度的段。一般的(如图1-9所示),固定窗口部分均匀的出现在整个数据集,例如对齐窗口。在这些例子中,对于不同的数据子集需要对窗口进行相移(例如每个key),使得窗口完成更均匀的分布,与之相对的例子是非对齐窗口,因为他们数据各不相同
滑动窗口(又叫跳窗)
固定窗口的一种泛化,滑动窗口是由固定长度和固定周期来定义的。如果周期大小小于长度,则窗口重叠。如果周期等于长度,则就是固定窗口。如果周期大于宠爱很难过度,则是一个怪异的采样窗口,它看起来是一个随时间变化的一个数据子集。和固定窗口一样,滑动窗口基本是对齐的,虽然他们能是不对齐在某些应用中因为性能优化。请注意在图1-8中的滑动窗口被画成给人一种滑动的感觉,而实际上,是五个窗口应用到整个数据集上。
会话窗口
动态窗口的一个例子,会话由事件序列组成,因超过一定时限的不活跃时间后终止。会话通常是被用来分析用户随时间的行为,通过聚集在一起形成时间相关的事件(如一次观看所形成的视频序列)。会话有趣的地方在于他们的长度不能被定义为一个先验的结果,他们取决于所涉及的实际数据。他们通常是非对齐窗口的经典例子,因为会话几乎在不同的数据子集之间从不相同。
我们之前讨论过两个时间范畴(处理时间和事件时间)是我们本质上所关注的两类。在两种范畴中窗口化都是合理的,因此我们详细的看下每种并且了解下他们之间的区别。因按处理时间的窗口化从历史上看很常见,所以我们先看它。
图图图图图图图
图1-9图图图图
按处理时间窗口化。当按处理时间对齐时,系统通常需要缓存到来的数据到窗口中直到一些处理时间已经过去了。例如,加入每五分钟的固定窗口,系统会缓存五分钟处理时间的数据,之后它会处理这五分钟观察到的所有数据所对应的窗口,然后发送他们到下游供处理。
按处理时间的窗口化有如下良好的属性。
它很简单。实现非常直接因为你不必担心在时间内的数据混洗。你只要缓存它们到来的数据然后在窗口关闭时候发送到下游。
判断窗口的完备性很直接。因为系统有针对看到的窗口是否有所有的输入数据相关的完整知识,它能做给定的窗口是否完整的准确判断。这就意味着不必有处理“迟到”数据的需求,当按照处理时间窗口化时候。
如果你想要观察到的数据源的推断信息,按处理时间的窗口所得的就是你想要的。许多监控场景都属于这类。设想下追踪每秒发送全球规模的web服务的请求数量。计算这些请求中检测中断的比例是一个按处理时间窗口化的准确使用场景。
撇开优点不谈,基于处理时间的窗口还有一个非常大的缺点。如果相关的数据与事件时间关联,当处理时间窗口是为了反应事件真实发生的情况时,这些数据必须按照事件时间序到来。不幸的是,在现实世界和分布式的输入源中事件时间序的数据是罕见的。
一个简单的例子,考虑有一个手机应用会手机使用数据用于之后的处理。有种场景是给定的移动设备离线了任意时间(短暂失去连接,可能是乘坐飞机开启了飞行模式等)。在这一周期内数据不会上传知道设备重新上线。这就意味着数据到达时距事件时间偏差几分钟、几小时、几天、几周甚至更多。当用处理时间作为窗口时这种情况基本不可能得出任何一种有用的结论。
另外一个例子,在系统整体稳定时,许多分布式的输入源看似提供了事件时间序(或几乎提供)的数据。不幸的是,而实际上数据源的事件时间偏差小并不意味着它一直是这样的。考虑一个全球的服务,它在多个大陆上处理收集上来的数据。如果横贯大陆的网络带宽受限导致了网络问题(遗憾的是,这出奇的普遍),它极大的降低了网络贷款或增加了延迟,这是一部分输入数据到来的偏差比之前大了,如果你想将这些数据按照处理时间窗口化,你的窗口将无法表征它们的真实发生时的情况,相反地,它们会代表事件到达处理管道的时间窗口,它任意的混合一些之前和现在的数据。
这些场景中我们真正想要窗口化数据其实是按照事件时间的,它针对迟来的事件更具鲁棒性。我们想要的就是按事件时间窗口化。
按事件时间窗口化。事件时间的窗口化是指你想将观察到的数据源按照事件发生的时间分成有限的块。这是窗口模式的“金本位”。早在2016年,大多数在使用的数据处理系统缺乏支持它的特性()。我很高兴的说时下完全不同了,有多个系统,从Flink到Spark再到Storm再到Apex,某种程度上都原生支持事件事件的窗口化了。
图1-10展示了从无界数据源到一小时固定窗口的例子。
图图图图图
图1-10图图图
在图1-10中黑色的箭头指向了两个特定的有趣的数据片。每一个按照处理事件到来的窗口并不与数据小部分所归属的事件事件窗口匹配。严格意义上来说,如果这些数据在关注事件时间的场景中按照处理事件窗口化了,计算出来的结果就会是不正确的。如你所愿,使用事件时间的窗口会带来事件时间结果的正确性。
事件时间窗口在无界数据源之上的另一个好处是你能创建动态大小的窗口,如会话,当在固定窗口之上生成会话时不会任意的切分所观察到的(如我们之前在“无界数据:流”中所看到的会话的例子),正如图1-11表现的那样。
图图图图图
图1-11
当然,有力的语义少有不需付出代价的,事件时间窗口也不例外。事件时间窗口有两个显著的弱点,这是由于窗口相较于窗口本事实际的长度必须存在的更久(与处理事件相比)。
缓冲
由于扩展窗口寿命,需要缓存的数据更多了。幸亏持久化的存储基本是大多数数据处理所依赖的最廉价的资源类型(其他的主要是CPU,网络带宽和内存)。严格意义上说,这个问题与你想使用强一致持久化状态和适当的内存缓存层相比不必太过担心。而且许多有用的聚合不要求全部的数据源都被缓存(如求和或平均值),可用增量执行所替代,这种方法更轻量而且中间聚合结果存储在持久化状态中。
完整性
假如我们没有什么好办法知道何时我们能看到所有的数据对于给定的窗口,我们怎么知道这个窗口的结果是准备好发生的呢?事实上,我们不可能。对于大多数类型的输入,系统能给出一个合理的准确的启发式的估算值,来表示窗口完成,例如像MillWheel、Cloud Dataflow和Flink(我们会在第三和四章多聊一些)所提到的水位线等机制。但对于某些明确正确性至上的场景中(考虑计费),唯一的选择就是为数据管道的构建提供一种方式,使其能在他们想要结果时让窗口显现并且让结果能够随时间推移能被改进。处理窗口的完备性(或缺乏完备性)是一个极具吸引力的课题但可能在具体的实例中进行探索,我们将会在下面介绍。
总结
长舒一口气!实在太多资料了。如果你已经成功吸收了,你就值得被称赞!但我们也才刚开始。在向前迈进之前我们详细看了Beam模型方法,让我们快速回顾下我们现在所学到的。这一章,我们完成了下述事情:
澄清术语。主要参考所构建无界数据的系统来讨论了流的定义,使用根据一些说明项例如近似/推测的结果来区分在流式这一大的概念下被分类的概念。额外的,我们突出了大规模数据集中的两个重要维度----基数(有界的和无界的)和编码(表VS流),我们会在本书的第二半部分引入更多的讨论。
评估了设计良好的批式和流式系统的相关性质,得出流式其实事实上是批式的严格超集,以及像是Lambda架构这些概念,它是以流式比不上批式为条件的设计,并且注定在流式系统后要被取代的架构。
提出两个对流式系统非常必要的高层概念来赶上和最终超越批式,分别是是正确性和时间归因工具
阐明事件时间和处理时间之间重要的不同,描述他们使用时所暴露的难点,主要集中他们产生和提出一个抛弃完整性概念的思想上的转变,转而适应随时间变化的数据。
看下主流的数据处理方法是如何处理有界和无界数据的,包括是批式的还是流式的引擎,粗略的对无界数据处理进行了分类:时间不可知、近似,按处理时间窗口化,按事件时间窗口化
接下来,我们深入Beam模型的细节,从概念上看下我们怎么将数据处理拆解为四个相关的维度:What,Where,When和How。我们也会详细看下处理一个简单地具体的例子中的数据集,从多个场景突出多元化的用例在Beam模型中是如何使用的,用一些具体的API让我们真正接触下现实情况。这些例子将会有助于理解在这一章节中所提到的事件时间和处理时间的概念,而且还会额外的探索一些新的概念,如水位线。