关注公众号

关注公众号

手机扫码查看

手机查看

喜欢作者

打赏方式

微信支付微信支付
支付宝支付支付宝支付
×

超越批处理的世界:流计算(二)

2020.10.26

  事件时间和处理时间

  为了能更好的说明无穷数据处理,就需要很非常清楚的理解时间域的内容。任何一个数据处理系统里,都包含两种典型的时间:

  1.事件时间(Event time):是指事件发生的时间。

  2.处理时间(Processing time):系统观察到事件发生的时间。

  不是所有的应用场景都关心事件时间(如果你的场景不用,你的日子就好过多了),但大部分都关注。随便举几个例子,比如一段时间里的用户行为刻画、计费应用和很多的异常检测应用。

  理想化的情况下,事件时间和处理时间应该总是相同的,即事件在它发生的同时就被处理了。但现实是残酷的,处理时间和事件时间之间的偏移不仅是非零的,还经常是由多种因素(如输入源、处理引擎和硬件)的特性所共同组合成的一个可变方程。可以影响这个偏移的因素包括:

  1.共享的资源使用情况:比如网络拥塞、网络分区或共享环境里的CPU使用情况。

  2.软件因素:如步分布系统逻辑、资源争夺等。

  3.数据自身的特征:包括键分布、吞吐量变化、失序导致的变化(比如乘坐飞机的旅客在飞机落地后把手机从飞行模型调整到正常模式,然后某些事件才发生)。

  因此,如果把在实际系统里的事件时间和处理时间的关系画出来,你很可能会得到类似图1这样的一些图。

  图里X轴代表系统里的事件时间,即事件发生的时间在某一点之前的所有事件,Y轴代表事件被处理的时间,即处理某事件数据时系统的时间。

  图中,黑色的虚线的斜率是1,代表了理想的情况,即事件时间和处理时间是一样的。红色的线代表现实的情况。在这个例子里,系统在处理时间开始阶段有一些延迟,随后趋于理想状况的同步,最后又产生了一些延迟。在理想情况和实际情况之间的水平距离则代表了处理时间和事件时间之间的偏移。本质上,偏移就是由处理管道产生的延迟。

  可见事件时间和处理时间之间的偏移并不是静态的,这就意味着如果你关注的是事件时间(比如事件确切发生的时间点),在你处理数据数据时不能只看数据被观察的时间(处理时间)。不幸的是,现在很多的流计算系统却是按照处理时间设计来处理无穷数据的。为了应对无穷数据集的无限的特性,这些系统一般都会提供一些把输入数据按时间分片的机制。下面会仔细的讨论分片机制,但其本质都是按时间把数据切割成有限的块。

  如果你真正关心的是正确性并希望分析的是事件时间,你就不能用处理时间来定义数据的时间边界(比如,用处理时间来分片),虽然现有的很多流计算系统是这么做的。鉴于事件时间和系统时间之间没有一个一致的关联,某些数据可能会被错误的分到按处理时间分片的数据片里,尽管它们的事件时间并不属于这个片。这可能是由于分布式系统内在的延迟,或是由于很多数据源的在线/离线特性所造成的。但后果就是准确性就无法得到保证。下面(包括下一篇博文)我会用一些案例来更详细地讨论这个问题。

  糟糕的是,即便是用事件时间来分片,情况也不那么美好。对于无穷数据,失序和偏移的变化给分片带来了另外一个问题:完整性,即既然无法预测事件时间和处理时间之间的偏移,你怎么能确定你获得了分片时间内的所有数据?对很多的真实数据,这个问题的答案是无法确定。现有的大部分数据处理系统都依赖某种完整性的想法,对于无穷数据而言这可能会带来严重的困难。

  我建议与其试图去把无穷的数据梳理成有限的信息片,我们应该设计这样的工具(系统),他们可以让我生活在这些复杂数据造成的不确定性中。当新的数据到来时,我们可以抽取或者更新旧数据。任何系统都应该能应对这些不确定性,去方便的优化完整性的概念,而不只是一个口头上的必须。

  在介绍我们是如何在Cloud Dataflow里面使用Dataflow模型去构建这样一个系统前,让我们再讲一些有用的背景知识:常见的数据处理模式。

  数据处理模式

  到目前为止,我们已经获得了足够的背景知识来开始研究处理无穷数据和有穷数据的常见的核心模型。下面我会在批处理和流计算两种引擎的环境下分别对两种处理模式进行介绍。这里我把微批处理和流计算归为一种,因为在这个层面上,他们没有什么特别大的区别。

  有穷数据处理

  处理有穷数据是很简单直接的,相信大家都比较的熟悉了。如下图(图2)所示,我们会先对左边非结构化的据进行操作。使用某种分析引擎(通常是批处理类型的,但一个设计良好的流计算引擎也能做的一样好),比如MapReduce,对这些数据做运算。最后得到图右边所示的有规则的结构化数据,并获得其内在的价值。

  左边有限的非结构化数据经过一个数据处理引擎的处理,转变成了右侧的相应的结构化数据。泰勒·阿克道制作

  尽管上述过程可能有无数多种变形的版本,但他们总体的模式是很简单的。更有趣的是如何处理无穷数据集,下面就让我们来看一看各种处理无穷数据的典型方法。我们从使用传统的批处理引擎开始,最后以使用专门为无穷数据集而设计的系统(例如大部分流计算或微批处理系统)来结束。

  无穷数据-批处理

  批处理引擎尽管不是设计来处理无穷数据的,但从它们诞生开始就已经被用来处理无穷数据集了。可以想像的是,这个方法一般都涉及到把无穷数据分片成一系列有穷数据集,再用批处理引擎来处理。

  固定的时间窗口

  批处理引擎最常见的处理无穷数据集的方法就是重复性地把输入数据按固定时间窗口分片,然后再把每个片当作一个独立有穷数据源进行处理。特别是像日志这样的数据源,事件被记录进有层级的文件系统,而日志文件的名字就对应于它们相应的时间窗口。第一感就会用这个(固定窗口)方法。因为本质上,在数据创建之前就已经进行了基于事件时间的排列来把数据写入适当的时间窗口了。

  然而在实际场景中,很多系统依然需要处理完整性的问题。例如,要是由于网络原因某些事件写入日志被延迟了,怎么办?要是你的所有日志都要被传送到一个通用的存储区后才能被处理,怎么办?要是事件是从移动设备上发送来的,怎么办?这些场景都会需要对原先的处理方法进行一定的修改(例如,延迟处理知道确保所有的时间片内的事件都收集齐;或如果有数据晚到了,就对整个时间窗口内的数据再处理一次)。

  无穷数据集先通过固定的时间窗口被采集整理成有穷数据,然后再通过重复运行批处理引擎来处理。

  会话单元

  更复杂的时间窗口策略可以是会话单元。这个方法把无穷数据进行了更细的划分,以方便批处理引擎来处理无穷数据。会话一般是被定义为活动(如某个特定用户)的时间周期,以一段时间的不活跃来判定结束。使用批处理引擎来计算会话单元时,也经常会碰到同一个会话被分到了两个单元里,就如图4里的红色块所示。这种情况是可以通过增加批次的大小来减少,但相应的延迟也就会增加。另外一个选择是增加额外的逻辑来把分到前一次运行里的会话补进本次运算,但这会带来额外的复杂度。

  无穷数据集先通过固定的时间窗口被采集整理成有穷数据,并再进一步划分成不同的会话单元,然后再通过重复运行批处理引擎来处理。

  使用传统的批处理引擎来计算会话单元还不是最理想的方法。一个更好的方法则是用流的方式来构建会话。下面我们就来讨论。

  无穷数据-流计算

  与基于批次的无穷数据处理方法的临时特性相反的是,流计算系统天生就是为无穷数据开发的。如前文所说的,对于很多现实世界里的分布式数据源,你不仅要应对数据的无穷性,还要处理下面两个特性:

  对应于事件时间的高度无序性。这意味着你需要某种程度上对事件做时间排序,如果你想按事件时间来做分析。

  时间漂移的变化性。这意味着在一个固定的Y时间的增量里你不能假定你可以看到大部分发生在对应的X时间增量范围内的事件。

  有多种可以处理有这样特性的数据集的方法。我大致把它们分成四类:

  1.时间不可知(Time-agnostic)

  2.近似算法(Approximation algorithms)

  3.按处理时间做时间窗口分片

  4.按事件时间做时间窗口分片

  下面就分别看一看这几种方法。

  时间不可知

  时间不可知处理方法的使用场景是当时间本质上无关,所有的逻辑仅关心数据本身而非时间。因为这种场景下只关心数据的到达,所以并不需要流计算引擎来做特殊的支持,只要保证数据的传递就可以了。因此,本质上现有的所有流计算系统都支持时间不可知场景(当然对于准确性有要求的用户,还需要排除那些对强一致性的保证不支持的系统)。批处理系统也能很好的支持时间不可知的无穷数据的应用场景,只要简单地把数据分成特定的有穷数据块序列,再依次处理即可。下面会介绍一些实际的例子,但考虑到这种场景的处理比较的容易理解,我不会用过多的篇幅。

  过滤(Filtering)

  非常基础的一个场景就是过滤。比如你要处理网站流量日志,想过滤掉不是来自某个特定域名的所有流量。那么就只要在数据到达的时候,检查一下是不是来自那个特定的域,如果不是就丢弃掉。这种场景只依赖于数据元素本身,因此跟数据源是否是无穷的、失序的或是延迟有变化的就没有关系了。

  不同类型的数据从图左向右流进,被过滤后形成了只包含一种类型数据的统一数据集。


推荐
关闭