Streaming Processing Concepts --streaming 101

第1章 现代数据处理概念

1.1 术语:什么是流媒体?

什么是流媒体?如今,“流”一词用于表示各种不同的事物,这可能会导致误解真正的流或真正的流系统。

问题的症结在于,许多事物应该用它们是什么来描述(例如,无限制的数据处理,近似结果等),而如何用通俗的方式来描述它们从历史上看,它们已经实现了(即通过流执行引擎)。术语的这种缺乏精确性掩盖了流传输的真正含义,并且在某些情况下,使流传输系统本身承受了这样的隐患,即其功能仅限于经常被描述为“流传输”的特征,例如近似或推测性结果。鉴于精心设计的流传输系统具有与任何现有批处理引擎一样能够产生正确,一致,可重复的结果的能力(技术上更是如此),所以将术语“流”隔离为非常具体的含义:一种数据处理引擎,设计时考虑了无限的数据集。而已。(为完整起见,值得一提的是,该定义包括真正的流和微批量实现。)

关于“流”的其他常见用法,以下是经常听到的一些用法,每个用法都有更精确的描述性术语。

1.1.1 无限数据:

一种不断增长的,本质上无限的数据集。这些通常称为“流数据”。但是,术语流或批处理在应用于数据集时会出现问题,因为如上所述,它们暗示使用某种类型的执行引擎来处理那些数据集。实际上,所讨论的两种类型的数据集之间的关键区别是它们的有限性,因此最好使用捕获这种区别的术语来表征它们。这样,将无限的“流”数据集称为无界数据,而有限的“批处理”数据集称为有界数据。

1.1.2 无限数据处理:

一种数据处理的持续模式,应用于上述类型的无限数据。自从批处理系统最初被构思出来以来,就已经使用批处理引擎的重复运行来处理无限制的数据(相反,精心设计的流系统不但能够处理受限数据上的“批处理”工作负载,还具有更多的能力)。这样,为了清楚起见,将简称为无限制数据处理。

1.1.3 低延迟,近似和/或推测性结果:

这些类型的结果最常与流引擎关联。传统上,批处理系统在设计时并未考虑到低延迟或推测性结果,这是历史的产物,仅此而已。当然,如果得到指示,批处理引擎完全有能力产生近似结果。因此,与上述术语一样,将这些结果描述为(低延迟,近似和/或推测)要好于通过历史(通过流引擎)将它们表现出来的方式更好。可以放心地假设为无限制数据集设计的执行引擎,仅此而已。当指上述任何其他术语时,将明确地说出无界限数据,无界限数据处理或低延迟/近似/推测性结果。

1.2 关于流媒体的极大限制

流媒体系统可以做什么和不能做什么,重点是可以。流媒体系统长期以来一直局限于提供低延迟,不准确/推测性结果的小众市场,通常将其与功能更强大的批处理系统结合使用,以提供最终正确的结果。即Lambda体系结构。对于尚未熟悉Lambda体系结构的用户,基本思想是将流系统与批处理系统一起运行,两者都执行基本相同的计算。流式系统为您提供了低延迟,不准确的结果(由于使用了近似算法,或者因为流式系统本身不提供正确性),一段时间后,批处理系统开始运行并为您提供正确的输出。最初由Twitter的Nathan Marz(creator of Storm),它最终获得了相当大的成功,流引擎对于正确性部门来说有点令人失望,而批处理引擎本来就像期望的那样笨拙,因此Lambda也提供了一种方法。不幸的是维护Lambda系统很麻烦:需要构建,配置和维护管道的两个独立版本,然后以某种方式最后合并来自两个管道的结果。Lambda体系结构的整个原理有点不好。Kreps在使用像Kafka这样的可重播系统作为流互连时解决了可重复性的问题,并提出了Kappa架构,这基本上意味着要使用一个设计合理的系统来运行单个管道,该系统针对该应用程序进行了适当的构建。

所有这一切的必然结果是,流媒体系统的广泛成熟以及用于无限制数据处理的强大框架相结合,将使Lambda体系结构及时降级到其所属的大数据历史上。实际上只需要两件事:

1.2.1 正确性 

正确性归结为一致的存储。流系统需要一种检查时间上的持久状态的方法(Kreps在他的“ 为什么本地状态是流处理中的基本原语”中谈到过)后),并且必须设计得足够好,以根据机器故障保持一致。几年前,当Spark Streaming首次出现在公共大数据场景中时,这是一个在黑暗的流媒体世界中保持一致性的灯塔。值得庆幸的是,此后情况有所改善,但值得注意的是,有多少流媒体系统仍在试图保持强一致性而努力。我真的不能相信最多只能进行一次处理,但事实是这样。重申一下,因为这一点很重要:一次处理就需要强的一致性,而正确性是必须的,这对于任何有机会满足或超过批处理系统能力的系统都是必需的。除非您真的不在乎结果,我恳请您避免使用任何无法提供高度一致状态的流系统。如果批处理系统能够产生正确的答案,则不需要您提前进行验证;不要在无法满足相同要求的流媒体系统上浪费时间。如果想进一步了解如何在流式传输系统中获得强大的一致性,建议您参考MillWheelSpark Streaming论文。这两篇论文都花费大量时间讨论一致性。

1.2.2 时间推理工具 

 这使你超越批处理。对时间进行推理的好工具对于处理事件-时间倾斜变化的无界、无序数据是必不可少的。越来越多的现代数据集表现出这些特征,并且现有的批处理系统(以及大多数流系统)缺乏必要的工具来应对它们带来的困难。

1.2.3 Event time and processing time

要说服无界限的数据处理,需要对所涉及的时间域有清楚的了解。在任何数据处理系统中,我们通常都会关注两个时间域:

事件时间,即事件实际发生的时间。

处理时间,这是在系统中观察到事件的时间。

在理想情况下,事件时间和处理时间将始终相等,事件发生时将立即进行处理。但是,实际情况并非如此,事件时间与处理时间之间的偏差不仅不为零,而且还经常是基础输入源,执行引擎和硬件的特征的高度可变的函数。可能影响偏斜程度的因素包括:

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

l  软件原因,例如分布式系统逻辑,争用等。

l  数据本身的特征,包括密钥分布,吞吐量差异或混乱差异。

如果在任何实际系统中绘制事件时间和处理时间的进度图,通常都会得到一些类似于图1中红线的图形。

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

斜率为1的黑色虚线表示理想的处理时间和事件时间完全相等;红线代表现实。在此示例中,系统在处理时间开始时稍微滞后,在中间偏向理想位置,然后再次向末尾滞后。理想线和红线之间的水平距离是处理时间和事件时间之间的偏斜。这种偏差本质上是处理管道引入的延迟。

由于事件时间与处理时间之间的映射不是静态的,因此,如果关心数据的事件时间(即事件实际发生的时间),就不能仅在管道中何时观察到数据的上下文中分析数据。这是大多数为无限制数据设计的现有系统的运行方式。为了应对无边界数据集的无限性质,这些系统通常提供一些窗口化输入数据的概念。这实际上意味着沿着时间边界将数据集切成有限的片段。

如果关心正确性,并且有兴趣在事件时间的背景下分析数据,则不能像大多数现有系统那样使用处理时间(即处理时间窗口)来定义这些时间边界。由于处理时间与事件时间之间没有一致的相关性,因此某些事件时间数据最终将在错误的处理时间窗口内结束(由于分布式系统固有的滞后性,多种类型的输入源的在线/离线特性,等),将正确性扔出窗外。

在数据不受限制的情况下,混乱和可变偏斜会引发事件时间窗口的完整性问题:在处理时间和事件时间之间缺乏可预测的映射,如何确定在给定事件时间X内观察到所有数据的时间?对于许多现实世界的数据源根本不能。当今使用的绝大多数数据处理系统都依赖某种完整性概念,这在将它们应用于无限制数据集时会处于严重的劣势。

第2章 数据处理方式

2.1 有界数据

处理边界数据非常简单,并且每个人都可能熟悉。在下图中,我们从左侧开始,其中包含充满熵的数据集。我们通过诸如MapReduce之类的数据处理引擎(通常是批处理,尽管设计良好的流引擎也可以正常运行)运行它,并在右侧最终生成具有更大内在价值的新结构化数据集:

图2:使用经典批处理引擎进行的边界数据处理。左侧的有限数量的非结构化数据通过数据处理引擎运行,从而在右侧产生了相应的结构化数据。

接下来让我们看一下通常处理无边界数据的各种方式,从传统批处理引擎所使用的方法开始,然后结束于为无边界数据设计的系统可以采用的方法,例如大多数流或微批处理引擎。

2.2 无限数据-批处理

批处理引擎虽然没有明确考虑到无边界数据的设计,但自从最初想到批处理系统以来,就已经使用它来处理无边界数据集。正如人们可能期望的那样,这种方法围绕着将无边界数据切成适合批处理的有边界数据集的集合。

2.2.1 Fixed windows

使用批处理引擎重复运行来处理无边界数据集的最常见方法是,将输入数据开窗到固定大小的窗口中,然后将这些窗口中的每一个作为独立的有边界数据源进行处理。特别是对于日志这样的输入源,事件可以写入名称和它们对应的窗口的目录和文件层次结构中,这种事情一开始看起来就非常简单,因为您实际上已经执行了基于时间的随机化来获取数据提前进入适当的事件时间窗口。

但是实际上,大多数系统仍然要处理完整性问题:如果由于网络分区而导致某些事件在发送到日志的途中被延迟怎么办?如果事件是在全球范围内收集的,并且必须在处理之前将其转移到一个公共位置,该怎么办?如果事件来自移动设备怎么办?这意味着可能需要某种缓解措施(例如,延迟处理,直到您确定所有事件都已收集到,或者每当数据延​​迟到达时,就给定窗口重新处理整个批次)。

图3:通过具有经典批处理引擎的ad hoc固定窗口进行的无限制数据处理。将无界数据集预先收集到有限的固定大小的有界数据窗口中,然后通过连续运行经典批处理引擎对其进行处理。

2.2.2 Sessions

当尝试使用批处理引擎将无边界的数据处理为更复杂的窗口化策略(例如会话)时,这种方法会更加崩溃。会话通常被定义为活动间隔(例如,针对特定用户),由活动间隔终止。使用典型的批处理引擎计算会话时,通常会遇到将会话拆分为多个批次的情况,如下图中的红色标记所示。可以通过增加批量大小来减少拆分的数量,但要以增加延迟为代价。另一个选择是添加其他逻辑以拼接来自先前运行的会话,但是以进一步复杂为代价。

图4:通过具有经典批处理引擎的ad hoc固定窗口将无界数据处理到会话中。将无界数据集预先收集到有限的固定大小的有界数据窗口中,然后通过连续运行经典批处理引擎将其细分为动态会话窗口。

无论哪种方式,使用经典的批处理引擎来计算会话都不理想。更好的方法是以流方式建立会话。

2.2.3 无限数据—流式传输

与大多数基于批处理的无边界数据处理方法的即席性质相反,流系统是为无边界数据构建的。如前所述,对于许多实际的分布式输入源,不仅发现自己正在处理无界数据,而且还处理以下数据:

事件时间高度无序,这意味着如果要在发生数据的上下文中分析数据,则需要在管道中进行某种基于时间的混洗。

事件时间偏斜的变化,意味着不能仅仅假设您将始终在时间Y的恒定ε内看到给定事件时间X的大部分数据。

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

l  Time-agnostic

l  Approximation

l  按处理时间开窗

l  按事件时间开窗

2.2.3.1      Time-agnostic

与时间无关的处理用于时间基本上无关紧要的情况下,即所有相关逻辑都是数据驱动的。由于有关这些用例的所有事情都是由更多数据的到来决定的,因此,除了基本的数据传递外,流媒体引擎实际上不需要支持任何特殊功能。结果,基本上所有现有的流系统都开箱即用地支持时间不可知的用例(一致性方面模数系统间的差异,当然对于那些关心正确性的人而言)。批处理系统也非常适合于无界限数据源的时间不可知的处理,只需将无界限源切成任意序列的有界数据集并独立处理这些数据集即可。

2.2.3.2      Filtering

时间不可知处理的一种非常基本的形式是过滤。假设正在处理Web流量日志,并且想要过滤掉并非来自特定域的所有流量。将查看到达的每个记录,查看它是否属于感兴趣的域,如果不属于,则将其删除。由于这种情况在任何时候都仅取决于单个元素,因此,数据源是无界的,无序的以及事件时间偏斜不同的事实是无关紧要的。

图5:过滤无限制的数据。各种类型的数据集合(从左向右流动)被过滤为包含单个类型的同类集合。

2.2.3.3      Inner-joins

另一个与时间无关的示例是内部联接(或哈希联接)。当联接两个无边界数据源时,如果只关心两个源中的元素到达时联接的结果,则逻辑上就没有时间性元素。看到一个来源的值后,可以简单地将其缓冲到持久状态;只需在来自其他来源的第二个值到达时发出联接的记录。(实际上,可能希望针对未忽略的部分联接使用某种垃圾收集策略,这可能是基于时间的。但是对于用很少或没有未完成的联接的用例来说,这样的事情可能不是问题。)

图6:对无边界数据执行内部联接。当观察到来自两个来源的匹配元素时,将生成联接。

2.2.3.4      Approximation algorithm

图7:计算无边界数据的近似值。数据通过复杂的算法运行,产生的输出数据或多或少看起来像另一侧的期望结果。

方法的第二大类是近似算法,例如近似Top-N流式K均值等。它们采用无限制的输入源并提供输出数据,如果斜视它们,它们或多或少看起来像希望得到。近似算法的优点是,根据设计,它们的开销很低,并且设计用于无限制的数据。缺点是它们的数量有限,算法本身通常很复杂(这使得很难构想出新算法),并且它们的近似性质限制了其实用性。

值得注意的是:这些算法通常在设计中确实包含一些时间要素(例如某种内置的衰减)。并且由于它们在到达元素时对其进行处理,因此该时间元素通常基于处理时间。这对于在近似值上提供某种可证明的误差范围的算法尤其重要。如果这些错误范围是根据顺序到达的数据确定的,则当您以不同的事件时间偏斜来输入算法无序数据时,它们实际上就没有任何意义。要记住的事情。近似算法本身是一个引人入胜的主题,但是由于它们本质上是时间不可知处理的另一个示例(以算法本身的时间特征为模),因此使用起来非常简单。

2.2.3.5      Windowing

其余两种用于无限制数据处理的方法都是窗口的变体。在深入探讨它们之间的差异之前,我应该先弄清楚窗口化的含义,因为只是简短地谈了一下。窗口化只是简单的概念,即获取一个数据源(无界或有界),并沿时间边界将其切成有限的块进行处理。下图显示了三种不同的窗口模式:

图8:示例窗口策略。为每个示例显示了三个不同的键,突出显示了对齐窗口(适用于所有数据)和未对齐窗口(适用于数据的子集)之间的差异。

  • 固定窗口:固定窗口将时间分割成具有固定大小的时间长度的片段。通常(如图8所示),将固定窗口的段均匀地应用于整个数据集,这是对齐窗口的一个示例。在某些情况下,希望为数据的不同子集(例如,每个键)对窗口进行相移,以在时间上更均匀地分散窗口完成负载,这是未对齐窗口的一个示例,因为它们在数据中会有所不同。
  • 滑动窗口:固定窗口的概括,滑动窗口由固定的长度和固定的时间段定义。如果周期小于长度,则窗口重叠。如果周期等于长度,则您有固定的窗口。而且,如果周期大于长度,那么您将获得一种奇怪的采样窗口,该窗口仅查看一段时间内数据的子集。与固定窗口一样,滑动窗口通常是对齐的,尽管在某些用例中作为性能优化可能未对齐。注意,图8中的滑动窗口是按原样绘制的,以提供滑动运动的感觉。实际上,所有五个窗口将应用于整个数据集。
  • 会话:动态窗口的一个示例,会话由事件序列组成,这些事件序列以不活动的间隔大于某个超时终止。会话通常用于通过将一系列与时间相关的事件(例如,一个坐着观看的视频序列)组合在一起来分析一段时间内的用户行为。会话很有趣,因为它们的长度不能事先定义。它们取决于所涉及的实际数据。它们也是未对齐窗口的典型示例,因为会话实际上在不同数据子集(例如,不同用户)之间永远不会完全相同。

2.2.3.6      Windowing by processing time

 

图9:按处理时间进入固定窗口。数据根据它们到达管道的顺序收集到窗口中。

当按处理时间开窗时,系统实质上会将传入的数据缓冲到窗口中,直到经过了一定数量的处理时间为止。例如,对于五分钟的固定窗口,系统将缓冲数据五分钟的处理时间,然后将其在那五分钟内观察到的所有数据视为一个窗口,并将其发送到下游进行处理。

处理时间开窗有一些不错的属性:

  • 很简单。该实现非常简单,因为不必担心会在一定时间内乱码数据。只需在事物到达时缓冲它们,然后在窗口关闭时将它们发送到下游。
  • 判断窗口的完整性很简单。由于系统具有关于是否已看到窗口的所有输入的完美知识,因此它可以对给定窗口是否完整做出完美的决策。这意味着在按处理时间进行窗口化处理时,无需以任何方式处理“后期”数据。
  • 如果想推断有关来源的信息,那么处理时间窗口正是您想要的。许多监视方案都属于此类。想象一下,跟踪每秒发送到全局Web服务的请求数。为检测中断而计算这些请求的比率是对处理时间窗口的完美利用。

抛开优点,处理时间窗口化有一个很大的缺点:如果相关数据具有与事件时间相关联的事件时间,则如果处理时间窗口要反映这些事件实际发生的时间,则这些数据必须按事件时间顺序到达发生了。不幸的是,事件时间排序的数据在许多实际的分布式输入源中并不常见。

举一个简单的例子,想象一下任何收集使用情况统计信息以供以后处理的移动应用程序。如果给定的移动设备在任何时间都处于离线状态(短暂的连接中断,在全国范围内飞行时处于飞行模式等),则在该时间段内记录的数据要等到该设备重新联机后才能上传。这意味着数据可能会以几分钟,几小时,几天,几周或更长时间的事件时间偏差到达。当按处理时间窗口化时,从这样的数据集中得出任何有用的推断基本上是不可能的。

再举一个例子,似乎许多分布式输入源在整个系统运行状况良好时提供事件时间排序(或几乎如此)的数据。不幸的是,健康时输入源的事件时间偏斜较低,但这并不意味着它将一直保持这种状态。考虑一个全球服务,该服务处理在多个大洲收集的数据。如果跨带宽受限的跨大陆线路(不幸的是,这很常见)的网络问题进一步降低了带宽和/或增加了延迟,那么突然会有一部分输入数据开始以比以前更大的偏差到达。如果要按处理时间对数据进行窗口化处理,则窗口将不再代表实际发生在其中的数据。相反,它们表示事件到达处理管道时的时间窗口,这是旧数据和当前数据的任意混合。

在这两种情况下,我们真正想要的是按照事件的时间对数据进行窗口化,从而对事件的到达顺序具有鲁棒性。我们真正想要的是事件时间窗口。

这张图展示了一个将一个不受限制的源窗口打开到一个固定窗口的例子:

当需要以有限的块观察数据源以反映那些事件实际发生的时间时,将使用事件时间窗口。这是开窗的黄金标准。令人遗憾的是,当今使用的大多数数据处理系统都缺乏对它的本机支持(尽管任何具有像样的一致性模型的系统,例如Hadoop或Spark Streaming,都可以充当构建此类窗口系统的合理基础)。

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

图10:按事件时间进入固定窗口。数据根据发生的时间收集到窗口中。白色箭头标出到达处理时间窗口的示例数据,这些时间窗口与它们所属的事件时间窗口不同。

图中的实白线标出了两个感兴趣的特定数据。这两个数据都到达与它们所属的事件时间窗口不匹配的处理时间窗口。这样,如果针对关注事件时间的用例将这些数据放入了处理时间窗口,则计算结果将是不正确的。正如人们所期望的那样,事件时间正确性是使用事件时间窗口的一件好事。

关于在无边界数据源上进行事件时间窗口化的另一件好事是,您可以创建动态大小的窗口,例如会话,而在固定窗口上生成会话时,不会观察到任何分裂(如我们先前在会话示例中从“无边界数据”中看到的—批处理”部分):

图11:按事件时间进入会话窗口。数据被收集到会话窗口中,这些窗口基于相应事件发生的时间来捕获活动突发。白色箭头再次指出将数据放入其正确的事件时间位置所必需的时间上的随机播放。

当然,强大的语义很少免费提供,事件时间窗口也不例外。事件时间窗口有两个明显的缺点,这是因为窗口(在处理时间中)必须经常比窗口本身的实际长度更长的寿命:

  • 缓冲:由于延长了窗口寿命,因此需要更多的数据缓冲。幸运的是,持久存储通常是大多数数据处理系统所依赖的资源类型中最便宜的(其他主要是CPU,网络带宽和RAM)。这样,与使用任何设计良好,具有高度一致的持久状态和良好的内存缓存层的数据处理系统相比,此问题通常比人们可能想到的要少得多。而且,许多有用的聚合不需要缓冲整个输入集(例如,求和或平均),而是可以增量方式执行,而持久性存储的中间聚合要小得多。
  • 完整性:鉴于我们通常无法很好地了解何时看到所有内容给定窗口的数据,我们如何知道何时可以实现窗口的结果?实际上,根本不这样做。对于许多类型的输入,系统可以通过MillWheel的水印之类的工具来给出窗口完成的合理准确的启发式估计。但是,在绝对正确性至高无上的情况下(再次考虑帐单),唯一的真实选择是为管道构建器提供一种方法,使其表达何时希望实现窗口的结果以及如何随着时间的推移完善这些结果。

第3章 结论

l  术语,特别是将流”的定义缩小到仅适用于执行引擎,同时对通常归入“流”范畴的不同概念使用更具描述性的术语,例如无边界数据近似/推测性结果

l  对比了设计良好的批处理和流系统的相对功能,认为流实际上是批的严格超集,并且像Lambda Architecture这样的概念基于流不如批处理而注定要作为流系统退休成熟。

l  两个高级概念,它们是流系统赶上并最终超过批处理所必需的,分别是正确性时间推理工具

l  建立了事件时间与处理时间之间的重要区别,描述了这些区别在发生数据的上下文中分析数据时所带来的困难,并提出了从完整性的概念转向简单地适应随时间变化的数据的方法。

l  通过批处理和流引擎研究了当今常用的有界和无界数据的主要数据处理方法,将无界方法粗略地分类为:时间不可知近似按处理时间开窗按事件时间开窗

posted @ 2020-08-27 14:07  Fire_Li  阅读(213)  评论(0编辑  收藏  举报