流式处理术语解释:Exactly-once与Effectively-once

分布式事件处理现在已经逐渐成为大数据领域的热点话题,主要使用的流处理引擎包括Storm、Flink、Spark(Spark Streaming)、HERON等等。其中“严格一次(Exactly-once)”是很多引擎追求的目标之一,很多引擎宣称可以提供“严格一次”的处理语义。

但是“严格一次”具体指的是什么,需要具备哪些能力,当SPE宣称可以支持时这实际上意味着什么对于这些问题还有很多歧义和误导。使用“严格一次”来描述处理语义本身就会造成很多语义上的误导。本文主要讨论各个引擎在“严格一次”处理语义方面的差异,“严格一次”为什么更适合称之为“Effectively-once”,以及各类常用技术之间要进行的取舍。

背景

流处理也被称为事件处理,简单说就是指持续不断地处理一系列无穷无尽的数据或事件的过程。流处理或事件处理应用程序大致可以看做是一种有向图,大部分情况也可以看做是DAG。图中每一个边缘代表一个数据或事件流,每个顶点代表使用应用程序定义的逻辑处理来自相邻边缘的数据或事件的运算符。两种特殊类型的顶点:通常称之为Source和Sink,Source会消耗外部数据/时间并将其注入应用程序而Sink通常负责收集应用程序生成的结果。下图展示了一个 典型的Heron处理拓扑:
流式处理术语解释:Exactly-once与Effectively-once
执行流/事件处理应用程序的SPE通常可供用户指定可靠性模式或处理语义,代表了在跨越整个应用程序图处理数据时所能提供的保证。保证是有一定意义的,可以假设由于网络、计算机等原因遇到失败而导致数据丢失的概率。在描述SPE可以为应用程序提供的数据处理语义时,通常会有三种模式:最多一次(At-most-once)、最少一次(At-least-once)、以及严格一次(Exactly-once)

最多一次

其实是一种“尽力而为”的方法。数据或事件可以保证被应用程序中的所有运算符最多处理一次。意味着如果在流应用程序最终成功处理之前就已经丢失,则不会额外试图重试或重新传输事件。
流式处理术语解释:Exactly-once与Effectively-once

最少一次

数据或事件可以保证被应用程序图中的所有运算符最少处理一次。通常意味着如果在流应用程序最终成功处理之前就已丢失,那么事件将从来源重播或重新传输。然而因为可以重新传输,有时一个事件可能被多次处理因此这种方式被称之为“最少一次”。下面的例子中第一个运算符最初处理事件时失败了,随后重试并成功,随后再次重试并再次成功,然后再次重试实际上不必要的。
流式处理术语解释:Exactly-once与Effectively-once

严格一次

事件保证被流应用程序中的所有运算符“严格一次”处理,即使遇到失败。

为了实现“严格一次”处理语义,通常主要会使用下列两种机制:

  1. 分布式快照/状态检查点
  2. 最少一次事件交付,外加消息去重

通过分布式快照/状态检查点方法实现的“严格一次”是由Chandy-Lamport分布式快照算法启发而来的。在这种机制中会定期为流应用程序中每个运算符的所有状态创建检查点,一旦系统中任何位置出现失败,每个运算符的所有状态会回滚至最新的全局一致检查点,回滚过程中所有处理工作会暂停。随后源也会重置为与最新检查点相符的偏移量整个应用程序基本上会被“倒带”到最新一致状态,并从该状态开始重置处理,下图中展示了这种机制的一些基本概念:
流式处理术语解释:Exactly-once与Effectively-once
上图中流应用程序在T1时候正在正常运行,并创建了状态检查点。但是在T2时候运算符在处理传入的数据时失败了,此时S=4这个状态值已经被保存到持久存储中而S=12状态值正位于运算符的内存中。为了调和这种矛盾,在T3时候处理图将状态回退至S=4,并“重播”流中直至最新状态前每个连续的状态并处理了每个数据。最终结果是有些数据被处理了但是这也没有问题,因为无论回滚多少次结果状态都是相同的。

实现“严格一次”另一种方法是实现至少一次事件交付同时在每个运算符一端进行事件去重,使用这种方法的SPE会重播失败的事件并再次尝试处理并从每个运算符中移除重复的事件,随后才将结果时间发送给用户在运算符中定义的逻辑。这种机制要求为每个运算符保存事务日志,借此才能追踪哪些事件已经处理过了。为此 SPE 通常会使用诸如 Google 的 MillWheel等机制。
流式处理术语解释:Exactly-once与Effectively-once

分布式快照与至少一次事件交付外加去重机制的对比

从语义角度来看,分布式快照以及至少一次事件交付外加去重。这两种机制可以提供相同的保证,然而由于两种机制在实现方面的差异可能会对性能产生巨大的影响。

基于分布式快照/状态检查点的SPE在性能方面开销可能是最低的,基本SPE只需要在通过流应用程序照常处理事件的过程之外发送少量特殊事件,而状态检查点操作可以在后台以异步的方式进行,但是对于大型流应用程序失败的概率将会更高。这会导致SPE需要暂停应用程序并回滚所有操作符的状态这样会对性能产生影响。流应用程序规模越大遇到失败的频率就会越高因此性能方面受到的影响也会越大,然而需要再次提醒的是,这种机制是非侵入式的,只会对资源的使用造成少量的影响。

至少一次事件交付外加去重机制可能需要更多的资源尤其是存储资源。在这种机制中,SPE需要追踪已经被运算符的每个实例成功处理的每个元组,借此才能执行去重并实现自身在每个事件中的去重。这可能需要追踪非常大量的数据,尤其是当流应用程序规模非常大,或运行了很多应用程序的时候。每个运算符中的每个事件执行去重操作,这本身也会产生巨大的性能开销。然而对于这种机制,流应用程序的性能不太可能受到应用程序规模的影响。对于分布式快照/状态检查点机制,如果任何运算符遇到任何失败,均需要全局暂停并状态回滚;对于至少一次事件交付外加去重机制,失败只能影响到局部。如果某个运算符遇到失败,只需要从上游来源重播 / 重新传输尚未成功处理的事件,对性能的影响可隔离在流应用程序中实际发生失败的地方,只会对流应用程序中其他运算符的性能产生最少量的影响。从性能的角度来看,两种机制各有利弊,具体情况可参阅下文表格。
流式处理术语解释:Exactly-once与Effectively-once