做者:宝牛算法
本文翻译自 streaml.io 网站上的一篇博文:“Exactly once is NOT exactly the same” ,分析了流计算系统中常说的『Exactly Once』特性,主要观点是:『精确一次』并不保证是彻底同样。主要内容以下:数据库
目前市面上使用较多的流计算系统有 Apache Storm,Apache Flink, Heron, Apache Kafka (Kafka Streams) 和 Apache Spark (Spark Streaming)。关于流计算系统有个被普遍讨论的特性是『exactly-once』语义,不少系统宣称已经支持了这一特性。可是,到底什么是『exactly-once』,怎么样才算是实现了『exactly-once』,人们存在不少误解和歧义。接下来咱们作下分析。后端
流处理(有时称为事件处理)能够简单地描述为是对无界数据或事件的连续处理。流或事件处理应用程序能够或多或少地被描述为有向图,而且一般被描述为有向无环图(DAG)。在这样的图中,每一个边表示数据或事件流,每一个顶点表示运算符,会使用程序中定义的逻辑处理来自相邻边的数据或事件。有两种特殊类型的顶点,一般称为 sources 和 sinks。sources读取外部数据/事件到应用程序中,而 sinks 一般会收集应用程序生成的结果。下图是流式应用程序的示例。网络
A typical stream processing topology异步
流处理引擎一般容许用户指定可靠性模式或处理语义,以指示它将为整个应用程序中的数据处理提供哪些保证。这些保证是有意义的,由于你始终会遇到因为网络,机器等可能致使数据丢失的故障。流处理引擎一般为应用程序提供了三种数据处理语义:最多一次、至少一次和精确一次。分布式
以下是对这些不一样处理语义的宽松定义:性能
这本质上是一『尽力而为』的方法。保证数据或事件最多由应用程序中的全部算子处理一次。 这意味着若是数据在被流应用程序彻底处理以前发生丢失,则不会进行其余重试或者从新发送。下图中的例子说明了这种状况。网站
At-most-once processing semanticsspa
应用程序中的全部算子都保证数据或事件至少被处理一次。这一般意味着若是事件在流应用程序彻底处理以前丢失,则将从源头重放或从新传输事件。然而,因为事件是能够被重传的,所以一个事件有时会被处理屡次,这就是所谓的至少一次。翻译
下图的例子描述了这种状况:第一个算子最初未能成功处理事件,而后在重试时成功,接着在第二次重试时也成功了,实际上是没有必要的。
At-least-once processing semantics
即便是在各类故障的状况下,流应用程序中的全部算子都保证事件只会被『精确一次』的处理。(也有文章将 Exactly-once 翻译为:彻底一次,刚好一次)
一般使用两种流行的机制来实现『精确一次』处理语义。
实现『精确一次』的分布式快照/状态检查点方法受到 Chandy-Lamport 分布式快照算法的启发[1]。经过这种机制,流应用程序中每一个算子的全部状态都会按期作 checkpoint。若是是在系统中的任何地方发生失败,每一个算子的全部状态都回滚到最新的全局一致 checkpoint 点。在回滚期间,将暂停全部处理。源也会重置为与最近 checkpoint 相对应的正确偏移量。整个流应用程序基本上是回到最近一次的一致状态,而后程序能够从该状态从新启动。下图描述了这种 checkpoint 机制的基础知识。
Distributed snapshot
在上图中,流应用程序在 T1 时间处正常工做,而且作了checkpoint。然而,在时间 T2,算子未能处理输入的数据。此时,S=4 的状态值已保存到持久存储器中,而状态值 S=12 保存在算子的内存中。为了修复这种差别,在时间 T3,处理程序将状态回滚到 S=4 并“重放”流中的每一个连续状态直到最近,并处理每一个数据。最终结果是有些数据已被处理了屡次,但这不要紧,由于不管执行了多少次回滚,结果状态都是相同的。
另外一种实现『精确一次』的方法是:在每一个算子上实现至少一次事件传递和对重复数据去重来。使用此方法的流处理引擎将重放失败事件,以便在事件进入算子中的用户定义逻辑以前,进一步尝试处理并移除每一个算子的重复事件。此机制要求为每一个算子维护一个事务日志,以跟踪它已处理的事件。利用这种机制的引擎有 Google 的 MillWheel[2] 和 Apache Kafka Streams。下图说明了这种机制的要点。
At-least-once delivery plus deduplication
如今让咱们从新审视『精确一次』处理语义真正对最终用户的保证。『精确一次』这个术语在描述正好处理一次时会让人产生误导。
有些人可能认为『精确一次』描述了事件处理的保证,其中流中的每一个事件只被处理一次。实际上,没有引擎可以保证正好只处理一次。在面对任意故障时,不可能保证每一个算子中的用户定义逻辑在每一个事件中只执行一次,由于用户代码被部分执行的可能性是永远存在的。
考虑具备流处理运算符的场景,该运算符执行打印传入事件的 ID 的映射操做,而后返回事件不变。下面的伪代码说明了这个操做:
Map (Event event) { Print "Event ID: " + event.getId() Return event }
每一个事件都有一个 GUID (全局唯一ID)。若是用户逻辑的精确执行一次获得保证,那么事件 ID 将只输出一次。可是,这是没法保证的,由于在用户定义的逻辑的执行过程当中,随时均可能发生故障。引擎没法自行肯定执行用户定义的处理逻辑的时间点。所以,不能保证任意用户定义的逻辑只执行一次。这也意味着,在用户定义的逻辑中实现的外部操做(如写数据库)也不能保证只执行一次。此类操做仍然须要以幂等的方式执行。
那么,当引擎声明『精确一次』处理语义时,它们能保证什么呢?若是不能保证用户逻辑只执行一次,那么什么逻辑只执行一次?当引擎声明『精确一次』处理语义时,它们其实是在说,它们能够保证引擎管理的状态更新只提交一次到持久的后端存储。
上面描述的两种机制都使用持久的后端存储做为真实性的来源,能够保存每一个算子的状态并自动向其提交更新。对于机制 1 (分布式快照 / 状态检查点),此持久后端状态用于保存流应用程序的全局一致状态检查点(每一个算子的检查点状态)。对于机制 2 (至少一次事件传递加上重复数据删除),持久后端状态用于存储每一个算子的状态以及每一个算子的事务日志,该日志跟踪它已经彻底处理的全部事件。
提交状态或对做为真实来源的持久后端应用更新能够被描述为刚好发生一次。然而,如上所述,计算状态的更新 / 更改,即处理在事件上执行任意用户定义逻辑的事件,若是发生故障,则可能不止一次地发生。换句话说,事件的处理能够发生屡次,可是该处理的效果只在持久后端状态存储中反映一次。所以,咱们认为有效地描述这些处理语义最好的术语是『有效一次』(effectively once)。
那么,当引擎声明『精确一次』处理语义时,它们能保证什么呢?若是不能保证用户逻辑只执行一次,那么什么逻辑只执行一次?当引擎声明『精确一次』处理语义时,它们其实是在说,它们能够保证引擎管理的状态更新只提交一次到持久的后端存储。
从语义的角度来看,分布式快照和至少一次事件传递以及重复数据删除机制都提供了相同的保证。然而,因为两种机制之间的实现差别,存在显着的性能差别。
机制 1(分布式快照 / 状态检查点)的性能开销是最小的**,由于引擎其实是往流应用程序中的全部算子一块儿发送常规事件和特殊事件,而状态检查点能够在后台异步执行。可是,对于大型流应用程序,故障可能会更频繁地发生,致使引擎须要暂停应用程序并回滚全部算子的状态,这反过来又会影响性能。流式应用程序越大,故障发生的可能性就越大,所以也越频繁,反过来,流式应用程序的性能受到的影响也就越大。然而,这种机制是非侵入性的,运行时须要的额外资源影响很小。
机制 2(至少一次事件传递加剧复数据删除)可能须要更多资源,尤为是存储**。使用此机制,引擎须要可以跟踪每一个算子实例已彻底处理的每一个元组,以执行重复数据删除,以及为每一个事件执行重复数据删除自己。这意味着须要跟踪大量的数据,尤为是在流应用程序很大或者有许多应用程序在运行的状况下。执行重复数据删除的每一个算子上的每一个事件都会产生性能开销。可是,使用这种机制,流应用程序的性能不太可能受到应用程序大小的影响。对于机制 1,若是任何算子发生故障,则须要发生全局暂停和状态回滚;对于机制 2,失败的影响更加局部性。当在算子中发生故障时,可能还没有彻底处理的事件仅从上游源重放/重传。性能影响与流应用程序中发生故障的位置是隔离的,而且对流应用程序中其余算子的性能几乎没有影响。从性能角度来看,这两种机制的优缺点以下。
分布式快照 / 状态检查点的优缺点:
至少一次事件传递以及重复数据删除机制的优缺点:
虽然从理论上讲,分布式快照和至少一次事件传递加剧复数据删除机制之间存在差别,但二者均可以简化为至少一次处理加幂等性。对于这两种机制,当发生故障时(至少实现一次),事件将被重放/重传,而且经过状态回滚或事件重复数据删除,算子在更新内部管理状态时本质上是幂等的。
在这篇博客文章中,我但愿可以让你相信『精确一次』这个词是很是具备误导性的。提供『精确一次』的处理语义实际上意味着流处理引擎管理的算子状态的不一样更新只反映一次。『精确一次』并不能保证事件的处理,即任意用户定义逻辑的执行,只会发生一次。咱们更喜欢用『有效一次』(effectively once)这个术语来表示这种保证,由于处理不必定保证只发生一次,可是对引擎管理的状态的影响只反映一次。两种流行的机制,分布式快照和重复数据删除,被用来实现精确/有效的一次性处理语义。这两种机制为消息处理和状态更新提供了相同的语义保证,可是在性能上存在差别。这篇文章并非要让你相信任何一种机制都优于另外一种,由于它们各有利弊。