Going Streaming:何时以及如何
我们只是观察批量引擎上窗口管道的执行情况。
但是,理想情况下,我们希望我们的结果具有较低的延迟,并且我们还希望本地处理无界数据源。切换到流媒体引擎是朝着正确方向迈出的一步,但是我们之前等待我们的输入被全部消耗以生成输出的策略已不再可行。输入触发器和水印。
什么时候:关于触发器的精彩事情是触发器是很棒的事情!
触发器提供了问题的答案:“在处理时间内是否实现了结果?”触发器声明何时在处理时间内发生窗口输出(尽管触发器本身可能根据其他时域中发生的事情做出这些决定,例如正如水印在事件时间域中的进展,我们将在稍后看到)。窗口的每个特定输出称为窗口的窗格。
虽然可以想象相当广泛的可能触发语义,但从概念上讲,只有两种通常有用的触发器类型,实际应用程序几乎总是使用其中一种或两者的组合来归结:重复更新触发器这些周期性地生成更新的窗格窗口随着其内容的发展而变化。这些更新可以在每个新记录中实现,也可以在一些处理时间延迟后发生,例如每分钟一次。重复更新触发器的周期选择主要是平衡延迟和成本的练习。
完整性触发器只有在认为该窗口的输入完成某个阈值后,才会实现窗口的窗格。这种类型的触发器与我们在批处理中熟悉的类似:只有在输入完成后才提供结果。基于触发器的方法的不同之处在于,完整性的概念限定在单个窗口的上下文中,而不是始终受限于整个输入的完整性。
重复更新触发器是流式系统中遇到的最常见的触发器类型。它们易于实现且易于理解,并且它们为特定类型的用例提供了有用的语义:对实体化数据集的重复(并最终一致)更新,类似于数据库世界中物化视图所获得的语义。
完整性触发器不常遇到,但提供的流式语义更接近经典批处理世界的流式语义。它们还提供了推理缺失数据和后期数据之类的工具的工具,我们在讨论驱动完整性触发的基础原语时会很快(以及在下一章)讨论这些工具:水印。
但首先,让我们从简单开始,看看一些基本的重复更新触发器。为了使触发器的概念更加具体,让我们继续向我们的示例管道添加最直接的触发器类型:触发器与每个新记录一起触发,如例2-3所示。
Example 2-3. Triggering repeatedly with every record
PCollection<KV<Team, Integer>> totals = input
.apply(Window.into(FixedWindows.of(TWO_MINUTES))
.triggering(Repeatedly(AfterCount(1))));
.apply(Sum.integersPerKey());
如果我们要在流引擎上运行这个新管道,结果将如图2-6所示
您可以看到我们现在如何为每个窗口获取多个输出(窗格):每个相应的输入一次。当输出流被写入某种表格时,这种触发模式很有效,您可以简单地轮询结果。无论何时查看表格,您都会看到给定窗口的最新值,这些值将随着时间的推移而趋于正确。
每个记录触发的一个缺点是它非常健谈。处理大规模数据时,汇总等聚合提供了一个很好的机会来减少流的基数而不会丢失信息。对于拥有高容量密钥的情况,这一点尤其明显;就我们的例子而言,拥有大量活跃球员的大型球队。
想象一下这是一个大型多人游戏,其中玩家被分成两个派系中的一个,你想要按照每个派系计算统计数据。对于给定阵营中的每个玩家,可能没有必要为每个新输入记录更新你的标签。相反,您可能会在一些处理时间延迟(比如每秒或每分钟)后更新它们。使用处理时间延迟的好处是它在00:00 / 00:00 高容量键或窗口之间具有均衡效果:得到的流最终会更加统一基数。
触发中处理时间延迟有两种不同的方法:对齐延迟(其中延迟将处理时间切换到跨键和窗口对齐的固定区域)和未对齐延迟(其中延迟相对于给定窗口内观察到的数据) )。具有未对齐延迟的管道可能如示例2-4所示,其结果如图2-7所示。
Example 2-4. Triggering on aligned two-minute processing-time boundaries
PCollection<KV<Team, Integer>> totals = input
.apply(Window.into(FixedWindows.of(TWO_MINUTES))
.triggering(Repeatedly(AlignedDelay(TWO_MINUTES)))
.apply(Sum.integersPerKey());
这种对齐的延迟触发器实际上是从像Spark Streaming这样的微型分流器系统中获得的。 关于它的好处是可预测性; 您可以同时在所有修改过的窗口中定期更新。 这也是缺点:所有更新都会立即发生,从而导致突发性工作负载通常需要更高的峰值配置才能正确处理负载。 另一种方法是使用未对齐的延迟。 这看起来像梁中的例2-5。 图2-8显示了结果。
Example 2-5. Triggering on unaligned two-minute processing-time boundaries
PCollection<KV<Team, Integer>> totals = input
.apply(Window.into(FixedWindows.of(TWO_MINUTES))
.triggering(Repeatedly(UnalignedDelay(TWO_MINUTES))
.apply(Sum.integersPerKey());
将图2-8中的未对齐延迟与图2-6中的对齐延迟进行对比,可以很容易地看出未对齐延迟如何在一段时间内更均匀地分散负载。任何给定窗口所涉及的实际延迟在两者之间有所不同,有时更多,有时更少,但最终平均延迟将保持基本相同。从这个角度来看,未对齐延迟通常是大规模处理的更好选择,因为它们会随着时间推移产生更均匀的负载分布。
重复更新触发器非常适用于我们只希望随着时间的推移定期更新结果的用例,并且这些更新收敛于正确性并且没有明确指示何时实现正确性。但是,正如我们在第1章中讨论的那样,分布式系统的变幻莫测常常导致事件发生的时间与管道实际观察到的时间之间存在不同程度的偏差,这意味着很难推断出输出的时间提供准确完整的输入数据视图。对于输入完整性很重要的情况,重要的是要有一些推理完整性的方法,而不是盲目地信任计算结果,这些结果是通过恰好已经找到管道的数据来计算的。输入水印。
00:00 / 00:00 时间:
水印
水印是问题答案的一个支持方面:“在处理时间内是否实现了结果?”水印是事件 - 时间域中输入完整性的时间概念。换句话说,它们是系统测量进度和完整性的方式,相对于在事件流中处理的记录的事件时间(有界或无界,尽管它们的有用性在无界情况下更明显)。
回想一下第1章中的这个图,稍微修改了图2-9,其中我描述了事件时间和处理时间之间的偏差,它是大多数真实世界分布式数据处理系统的一个不变的时间函数。
我声称代表现实的那条蜿蜒的红线基本上就是水印;它随着处理时间的推移捕获事件时间完整性的进度。从概念上讲,您可以将水印视为一个函数F(P)→E,它取决于处理时间并返回事件时间点。事件时间中的那一点E是系统认为所有输入事件时间小于E的输入点。换句话说,这是一个断言,没有更多的事件时间小于E的数据将再次被看到。根据水印的类型,完美或启发式,该断言可以分别是严格的保证或有根据的猜测:完美的水印对于我们对所有输入数据有完全了解的情况,可以构建完美的水印。在这种情况下,没有后期数据这样的东西;所有数据都是早期或准时的。
启发式水印对于许多分布式输入源,完美的输入数据知识是不切实际的,在这种情况下,下一个最佳选择是提供启发式水印。启发式水印使用有关输入的任何可用信息(分区,分区内的排序,如果有的话,文件的增长率等)来提供尽可能准确的进度估计。在许多情况下,这种水印在其预测中可以非常准确。即便如此,使用启发式水印意味着它有时可能是错误的,这将导致后期数据。我们将向您展示很快处理迟到数据的方法。
因为它们提供了相对于我们输入的完整性概念,所以水印形成了前面提到的第二类触发器的基础:完整性触发器。水印本身是一个引人入胜且复杂的主题,正如你在第3章深入探讨Slava的水印时所看到的那样。但是现在,让我们通过更新我们的示例管道以利用基于水印的完整性触发来查看它们。如实施例2-6中所示。
例2-6。水印完整性触发
PCollection<KV<Team, Integer>> totals = input
.apply(Window.into(FixedWindows.of(TWO_MINUTES))
.triggering(AfterWatermark()))
.apply(Sum.integersPerKey());
现在,一个有趣的水印质量是它们是一类函数,意味着有多个不同的函数F(P)→E满足水印的属性,取得不同程度的成功。正如我前面提到的,对于您对输入数据有完全了解的情况,可能会建立一个完美的水印,这是理想的情况。
但是,如果您缺乏对输入的完全了解或计算完美水印的计算成本太高,您可能会选择使用启发式来定义水印。
我想在这里提出的观点是,使用的给定水印算法独立于管道本身。我们不打算详细讨论在这里实现水印的意义(Slava在第3章中做了)。现在,为了帮助推动这个想法,即给定的输入集可以应用不同的水印,让我们看一下示例2-6中的管道,当在同一数据集上执行但使用两个不同的水印实现时(图2-) 10):在左边,一个完美的水印;在右边,一个启发式水印。
在这两种情况下,当水印通过窗口的末端时,窗口被实现。正如您所料,完美的水印可以随着时间的推移完美地捕捉管道的事件时间完整性。相比之下,用于右侧启发式水印的特定算法未考虑9的值,这在输出等待时间和正确性方面都极大地改变了物化输出的形状(如错误答案所示) 5为[12:00,12:02]窗口提供的)。
图2-9中的水印触发器和我们在图2-5到2-7中看到的重复更新触发器之间的巨大差异在于水印为我们提供了一种推理输入完整性的方法。
在系统实现给定窗口的输出之前,我们知道系统还不相信输入是完整的。这对于您想要推断输入中缺少数据或缺少数据的用例尤为重要。
图2-10。具有完美(左)和启发式(右)水印的流引擎上的窗口求和00:00 / 00:00
缺少数据用例的一个很好的例子是外连接。如果没有像水印一样的完整性概念,你怎么知道何时放弃并发出部分连接而不是继续等待连接完成?你没有。
并且基于处理时间延迟做出决定,这是缺乏真正水印支持的流媒体系统的常用方法,并不是一种安全的方法,因为我们在第1章中讨论了事件时间偏差的可变性:只要偏差仍然小于所选择的处理时间延迟,您的缺失数据结果将是正确的,但任何时候偏差超过该延迟,它们将突然变得不正确。从这个角度来看,事件时间水印是许多真实流媒体用例的关键部分,它必须推断输入中缺少数据,例如外连接,异常检测等。
现在,话虽如此,这些水印示例还突出了水印的两个缺点(以及任何其他完整性概念),特别是它们可能是以下之一:
太慢 当任何类型的水印由于已知的未处理数据而被正确延迟时(例如,由于网络带宽限制而导致输入日志缓慢增长),如果水印的进展是您唯一依赖于刺激结果的东西,则会直接转换为输出延迟。
这在图2-10的左图中最为明显,即使这些窗口的输入数据提前完成,迟到的9将保留所有后续窗口的水印。这对于第二个窗口[12:02,12:04]尤其明显,从窗口中的第一个值发生到我们看到窗口的任何结果之前需要将近7分钟。在这个例子中,启发式水印并没有遭遇相同的问题(输出前五分钟),但不要认为启发式水印从不会受到水印滞后的影响;这实际上只是我在这个具体例子中选择省略启发式水印的记录的结果。
这里重点如下:尽管水印提供了一个非常有用的完整性概念,但从延迟的角度来看,取决于产生输出的完整性通常并不理想。想象一个仪表板,其中包含有价值的指标,按小时或天显示。您不太可能想要等待整整一小时或一天才能看到当前窗口的结果;这是使用传统批处理系统为这些系统供电的难点之一。相反,随着输入的发展并最终变得完整,随着时间的推移,这些窗口的结果会变得更好,这样会更好。
太快 当启发式水印错误地提前超过应有的水平时,水印之前的事件时间数据可能会在一段时间后到达,从而创建延迟数据。这就是右边示例中发生的情况:在观察到该窗口的所有输入数据之前,水印超过了第一个窗口的末尾,导致输出值不正确而不是14。这个缺点严格来说是启发式水印问题;他们的启发性意味着他们有时会出错。因此,如果您关心正确性,单靠它们来确定何时实现输出是不够的。
在第1章中,我做了一些相当有力的陈述,关于完整性概念不足以满足大多数需要对无界数据流进行强有力的无序处理的用例。这两个缺点 - 水印太慢或太快 - 是这些论点的基础。您只是无法从仅依赖于完整性概念的系统中获得低延迟和正确性。那么,对于你真正想要两全其美的案例,一个人该做什么?好吧,如果重复更新触发器提供低延迟更新但无法推断完整性,并且水印提供了完整性但可变且可能的高延迟的概念,为什么不将它们的功能组合在一起?
什么时候:早/准/晚触发FTW!
我们现在看看两种主要类型的触发器:重复更新触发器和完整性/水印触发器。在许多情况下,它们中的任何一个都不够,但它们的组合在一起。 Beam通过提供标准水印触发器的扩展来识别这一事实,该触发器还支持水印两侧的重复更新触发。这被称为早期/准时/晚期触发器,因为它将由复合触发器实现的窗格分为三类:零或更多早期窗格,这是重复更新触发器的结果,定期触发直到水印通过窗口的末端。这些发射产生的窗格包含推测结果,但允许我们随着新输入数据的到来观察窗口随时间的演变。这补偿了水印有时太慢的缺点。
单个准时窗格,这是在水印通过窗口结束后完成/水印触发器触发的结果。这种触发是特殊的,因为它提供了一个断言,系统现在认为该窗口的输入是完整的。这意味着现在可以安全地推断缺失数据;例如,在执行外部联接时发出部分联接。
零个或多个后期窗格,它是另一个(可能是不同的)重复更新触发器的结果,它会在水印通过窗口结束后定期触发任何时间后期数据到达。在完美水印的情况下,后期窗格总是零。
但是在启发式水印的情况下,水印未能正确解释的任何数据都将导致后期触发。这补偿了水印太快的缺点。
让我们看看这看起来如何。我们将更新我们的管道以使用定期处理时间触发器,对于早期的7次发射,对齐延迟为1分钟,并且对于后期发射,每次记录触发器。这样,早期的启动将为我们提供大量窗口的批量处理(由于触发器每分钟只会触发一次,无论窗口的吞吐量如何),但我们不会引入不必要的延迟对于晚期发射,如果我们使用相当准确的启发式水印,这些希望有点罕见。在Beam中,看起来是例2-7
例2-7。 通过早期/准时/晚期API进行早期,准时和晚期点火
PCollection<KV<Team, Integer>> totals = input
.apply(Window.into(FixedWindows.of(TWO_MINUTES))
.triggering(AfterWatermark()
.withEarlyFirings(AlignedDelay(ONE_MINUTE))
.withLateFirings(AfterCount(1))))
.apply(Sum.integersPerKey());
(图2-11显示了结果)。
这个版本比图2-9有两个明显的改进:对于第二个窗口中的“水印太慢”情况,[12:02,12:04]:我们现在每分钟提供一次定期的早期更新。在完美的水印情况下,差异是最明显的,对于这种情况,首次输出时间从几乎七分钟减少到三分半;但在启发式案例中也明显改善了。现在,两个版本都提供了随时间推移的稳定细化(窗格值为7,10,然后是18),输入变得完整和窗口最终输出窗格的实现之间的延迟相对最小。
对于第一个窗口中的“启发式水印太快”情况,[12:00,12:02]:当值9显示较晚时,我们立即将其合并到一个值为14的新的已更正窗格中。
这些新触发器的一个有趣的副作用是它们有效地规范了完美和启发式水印版本之间的输出模式。虽然图2-10中的两个版本截然不同,但这里的两个版本看起来非常相似。它们看起来与图2-6到2-8中的各种重复更新版本更相似,但有一个重要区别:由于使用了水印触发器,我们还可以推断出我们生成的结果中的输入完整性早期/准时/晚期触发。这使我们能够更好地处理关注缺失数据的用例,例如外连接,异常检测等。
此时完美版和启发式早期/开启时间/晚期版本之间最大的差异是窗口生命周期限制。在完美的水印情况下,我们知道在水印结束后我们永远不会再看到窗口的数据,因此我们可以在那时删除窗口的所有状态。在启发式水印的情况下,我们仍然需要保持一个窗口的状态一段时间来考虑后期数据。
但到目前为止,我们的系统没有任何好的方法可以知道每个窗口需要保持多长时间状态。这就是允许迟到的地方。
何时:允许延迟(即垃圾收集)
在继续讨论我们的最后一个问题(“如何改进结果?”)之前,我想谈谈在长期无序的流处理系统中的实际必要性: 垃圾收集。在图2-11中的启发式水印示例中,每个窗口的持久状态在示例的整个生命周期中徘徊;这是必要的,以便我们能够在他们到达时适当处理迟到的数据。但是,虽然能够保持所有持久状态直到时间结束会很棒,但实际上,在处理无限数据源时,无限期地保持给定窗口的状态(包括元数据)通常是不切实际的. 我们最终会耗尽磁盘空间(或者至少为此付出代价,因为旧数据的价值随着时间的推移而减少)。
因此,任何真实的无序处理系统都需要提供一些方法来限制它正在处理的窗口的生命周期。一种干净简洁的方法是通过定义系统内允许的迟到范围;也就是说,限制任何给定记录的延迟时间(相对于水印),系统要对其进行处理;任何在此视野之后到达的数据都会被删除。在你限制了个别数据的延迟之后,你还确切地确定了窗口的状态必须保持多长时间:直到水印超过窗口末端的延迟范围。
但此外,您还可以让系统在被观察后立即立即丢弃任何数据,这意味着系统不会浪费资源处理数据,这是人们无关紧要的。
测量延迟使用导致后期数据的度量(即启发式水印)来指定处理后期数据的地平线似乎有点奇怪。从某种意义上说,它是。但是在可用的选项中,它可以说是最好的。唯一的另一个实际选择是指定处理时间的范围(例如,在水印通过窗口结束后保持窗口处理10分钟的处理时间),但使用处理时间会使垃圾收集策略容易受到问题的影响在管道本身内(例如,工人崩溃,导致管道停转几分钟),这可能导致窗口实际上没有机会处理他们原本应该拥有的后期数据。通过在事件时域中指定范围,垃圾收集直接与管道的实际进度相关联,这降低了窗口错过其适当处理后期数据的机会的可能性。
但请注意,并非所有水印都是相同的。当我们在本书中谈到水印时,我们通常会提到低水位标记,它们会悲观地尝试捕获系统所知道的最旧的未处理记录的事件时间。通过低水印处理迟到的好处是它们能够适应事件时间偏差的变化;无论管道中的偏差有多大,低水印都将始终跟踪系统已知的最古老的突出事件,从而提供可能的正确性的最佳保证。
相反,一些系统可能使用术语“水印”来表示其他事物。例如,Spark Structured Streaming中的水印是高水印,它乐观地跟踪系统所知道的最新记录的事件时间。当处理迟到时,系统可以自由地收集任何比通过某个用户指定的延迟阈值调整的高水印更旧的窗口。换句话说,系统允许您指定预期在管道中看到的最大事件时间偏差量,然后丢弃该偏斜窗口之外的任何数据。如果管道中的偏差保持在某个恒定的增量范围内,这可以很好地工作,但是比低水印方案更容易丢弃数据。
因为允许的迟到和水印之间的相互作用有点微妙,所以值得看一个例子。让我们从示例2-7 /图2-11中获取启发式水印管道,并在示例2-8中添加一分钟的延迟范围(请注意,这个特定的地平线已被严格选择为88,因为它非常适合图表;对于实际-world用例,更大的范围可能更实用):
例2-8 早期/准时/晚期开火,允许迟到
PCollection<KV<Team, Integer>> totals = input
.apply(Window.into(FixedWindows.of(TWO_MINUTES))
.triggering(
AfterWatermark()
.withEarlyFirings(AlignedDelay(ONE_MINUTE))
.withLateFirings(AfterCount(1)))
.withAllowedLateness(ONE_MINUTE))
.apply(Sum.integersPerKey());
此管道的执行类似于图2-12,其中我添加了以下功能以突出显示允许延迟的效果:表示处理时间中当前位置的粗黑线现在用带有指示迟到的刻度标注所有活动窗口的地平线(在事件时间内)。
当水印通过窗口的延迟时间范围时,该窗口关闭,这意味着窗口的所有状态都被丢弃。我留下一个虚线矩形,显示窗口在关闭时所覆盖的时间范围(在两个域中),一条小尾巴向右延伸,表示窗口的晚期视界(用于与水印形成对比)。
仅对于此图,我为第一个窗口添加了一个额外的后期数据,其值为6. 这6个是迟到的,但仍然在允许的延迟范围内,因此被合并到值为11的更新结果中。然而,9到达迟到的地平线,所以它只是被丢弃了。
关于迟到视野的两个最后侧面说明:绝对清楚的是,如果您正在消耗可获得完美水印的数据源,则无需处理延迟数据,并且允许的零秒延迟时间将是最佳的。这就是我们在图2-10的完美水印部分中看到的内容。
即使在使用启发式水印时,需要指定延迟视野的规则的一个值得注意的例外是类似于有限数量的密钥计算全局聚合(例如,计算到您网站的总访问次数)在任何时候,按网络浏览器系列分组)。在这种情况下,系统中活动窗口的数量受到使用中受限密钥空间的限制。只要键的数量保持在可控制的低水平,就不必担心通过允许的延迟来限制窗口的寿命。
实用性已经过时了,让我们继续讨论我们的第四个也是最后一个问题。
如何:累积
当触发器用于为单个窗口生成多个窗格时,我们发现自己遇到了最后一个问题:“结果的细化如何相关?”在我们到目前为止看到的示例中,每个连续窗格都是建立在它前面的那个之上。但是,实际上有三种不同的累积模式:丢弃每次实现窗格时,都会丢弃任何存储的状态。这意味着每个连续的窗格都独立于之前的任何窗格。当下游消费者自己进行某种积累时,丢弃模式很有用;例如,当将整数发送到期望接收增量的系统时,它将相加以产生最终计数。
累积如图2-6到2-11所示,每次实现窗格时,都会保留任何存储状态,并且将来的输入将累积到现有状态。这意味着每个连续的窗格都基于先前的窗格。当后续结果可以简单地覆盖以前的结果时,累积模式很有用,例如将输出存储在HBase或Bigtable等键/值存储中时。
累积和缩回这类似于累积模式,但在生成新窗格时,它还会为前一个窗格生成独立的缩进。撤回(与新的累积结果相结合)本质上是一种明确的说法“我之前告诉过你的结果是X,但我错了。
摆脱XI最后一次告诉你,并用Y替换它。“有两种情况下撤消特别有用:当下游消费者按不同维度重新组合数据时,新值最终可能最终被键入8 9 92与先前的值不同,因此最终在不同的组中。在这种情况下,新值不能只覆盖旧值;您需要撤消以删除旧值当动态窗口(例如,我们稍后会仔细查看的会话)正在使用时,由于窗口合并,新值可能会替换多个先前窗口。
在这种情况下,可能难以仅从新窗口确定哪个旧窗口正被替换。对旧窗口进行显式撤消使得任务变得简单。我们在第8章中详细介绍了这方面的一个例子。
当看到并排时,每个组的不同语义更加清晰。考虑图2-11中的第二个窗口(具有事件时间范围[12:06,12:08)的窗格)的两个窗格(具有早期/准时/晚触发的窗格)。表2-1显示了三种累积模式下每个窗格的值是什么样的(累积模式是图2-11中使用的特定模式)。
表2-1。使用图2-11中的第二个窗口比较累积模式让我们仔细看看发生了什么:
丢弃
每个窗格仅包含在特定窗格期间到达的值。因此,观察到的最终值并未完全捕获总和。但是,如果您要自己对所有独立窗格求和,则会得到12的正确答案。这就是当下游消费者本身在物化窗格上执行某种聚合时,丢弃模式很有用的原因。
累积如图2-11所示,每个窗格都包含在该特定窗格期间到达的值,以及先前窗格中的所有值。因此,正确观察到的最终值会捕获12的总和。但是,如果您要自己总结各个窗格,那么您实际上会对窗格1中的输入进行重复计算,从而得出不正确的总和15这就是为什么当你可以用新值覆盖以前的值时,累积模式最有用的原因:新值已经包含了到目前为止看到的所有数据。
累积和缩进每个窗格都包含新的累积模式值以及前一个窗格值的缩进。因此,观察到的最后一个值(不包括撤回)以及所有物化窗格的总和(包括撤消)都为您提供了12的正确答案。这就是撤消如此强大的原因。
例2-9展示了实施中的丢弃模式,说明了我们对例2-7所做的更改:例2-9。丢弃早期/准时/晚期发射的模式版本
PCollection<KV<Team, Integer>> totals = input
.apply(Window.into(FixedWindows.of(TWO_MINUTES))
.triggering(
AfterWatermark()
.withEarlyFirings(AlignedDelay(ONE_MINUTE))
.withLateFirings(AtCount(1)))
.discardingFiredPanes())
.apply(Sum.integersPerKey());
在具有启发式水印的流引擎上再次运行会产生如图2-13所示的输出。
尽管输出的整体形状类似于图2-11中的累积模式版本,但请注意此丢弃版本中的任何窗格都不会重叠。 结果,每个输出独立于其他输出。
如果我们想要查看撤消操作,则更改将类似,如示例2-10中所示。
例2-10。 累积和缩回模式版本的早期/准时/晚期点火
PCollection<KV<Team, Integer>> totals = input
.apply(Window.into(FixedWindows.of(TWO_MINUTES))
.triggering(
AfterWatermark()
.withEarlyFirings(AlignedDelay(ONE_MINUTE))
.withLateFirings(AtCount(1)))
.accumulatingAndRetractingFiredPanes())
.apply(Sum.integersPerKey());
因为每个窗口的窗格都是重叠的,所以清楚地看到缩回是有点棘手的。 回缩用红色表示,它与重叠的蓝色窗格结合,产生略带紫色的颜色。 我还在给定窗格中稍微水平移动了两个输出的值(并用逗号分隔),以便更容易区分。
图2-14结合了图2-9,2-11的最终帧(仅启发式)和并排,为三种模式提供了良好的视觉对比度。
图2-14。
积累模式的并排比较正如您所能想象的那样,所呈现的顺序中的模式(丢弃,累积,累积和缩回)在存储和计算成本方面各自相继成本更高。 为此,累积模式的选择提供了另一个维度,用于在正确性,延迟和成本方面进行权衡。
摘要。
完成本章后,您现在可以理解强大的流处理的基础知识,并准备好进入世界并做出惊人的事情。当然,还有八个章节焦急地等着你的注意力,所以希望你现在不会像现在这样,这一刻。但无论如何,让我们回顾一下我们刚才所涵盖的内容,以免你匆忙忘记任何一个问题。
首先,我们谈到的主要概念:事件时间与处理时间事件发生时和数据处理系统观察事件之间的最重要区别。
1.窗口: 通过在时间边界上切片来管理无界数据的常用方法(在处理时间或事件时间内,尽管我们将波束模型中窗口的定义缩小到仅在事件时间内)。
2.触发: 用于精确指定输出实现的声明机制对您的特定用例有意义。
3.水印: 事件时间进展的强大概念,提供了一种在无界数据上运行的无序处理系统中推理完整性(从而丢失数据)的方法。
4.积累:单个窗口的结果细化之间的关系,用于随着它的发展而多次实现的情况。
第二,我们用来构建探索的四个问题:计算出哪些结果? =转换。
事件时间在哪里计算结果? =窗口。
在处理时间内是否实现了结果? =触发器加水印。
结果的细化如何相关? =积累。
第三,为了推动这种流处理模式所提供的灵活性(因为最终,这就是真正的意义所在:平衡竞争紧张,如正确性,延迟和成本),回顾一下我们输出的主要变化能够通过最少量的代码更改实现相同的数据集:
所有这一切,在这一点上,我们真的只看了一种窗口:在事件时间固定窗口。正如我们所知,窗口有许多维度,在我们用Beam模型称之前,我想先触及至少两个维度。然而,首先,我们将稍微绕道而行,深入了解水印世界,因为这些知识将有助于构建未来的讨论(并且本身就很有吸引力)。进入Slava,舞台右…
如果你有幸阅读本书的Safari版本,那么就像“Streaming 102”一样,你有完整的延时动画。对于打印,Kindle和其他电子书版本,有静态图像,链接到网络上的动画版本。
跟我来这儿。在O’Reilly出版物中严格禁止通过复合标点符号(即表情符号)的细粒度情感表达。
事实上,我们只使用Beam中的原始触发器功能。回想起来,我们有点过火了。未来的迭代将更简单,更容易使用,在本书中,我只关注可能以某种形式保留的碎片。
更准确地说,函数的输入实际上是在管道中点的所有上游的时间P处的状态,其中水印是的观察:输入源,缓冲数据,正在处理的数据等;但从概念上讲,将其视为从处理时间到事件时间的映射更为简单。
请注意,我特意选择从启发式水印中省略9的值,因为它将帮助我对后期数据和水印滞后做出一些重要的观点。
实际上,启发式水印可能同样省略一些其他值,这反过来可能对水印的影响明显减小。如果从水印中筛选出迟到的数据是您的目标(在某些情况下非常有效,例如滥用检测,您只想尽快查看大部分数据),则不一定如此想要一个启发式水印而不是一个完美的水印。你真正想要的是百分位水印,它从计算中明确地降低了一些迟到数据的百分位数。
这并不是说没有用例主要关注正确性而不是延迟;在这些情况下,使用准确的水印作为管道输出的唯一驱动因素是一种合理的方法。
并且,正如我们之前所知,在使用完美水印的情况下,或者在启发式水印的情况下,这种断言要么得到保证,要么是有根据的猜测。
您可能会注意到逻辑上应该是第四种模式:丢弃和撤消。在大多数情况下,该模式并不十分有用,所以我在此不再进一步讨论。
回想起来,选择一组更加面向物化流中数据的观察性质(例如“输出模式”)而不是描述产生这些数据的状态管理语义的名称可能会更加清晰。 。也许:丢弃模式→增量模式,累积模式→值模式,累积和缩回模式→值和退回模式?然而,丢弃/累积/累积和缩回名称被包含在梁模型的1.x和2.x谱系中,所以我不想通过偏离在书中引入潜在的混淆。此外,很有可能累积模式将与Beam 3.0以及引入触发器触发器更加融合到背景中;当我们在第8章讨论SQL时,更多关于这一点.