流计算中的 Exactly Once 语义

扫码关注公众号免费阅读全文:冰山烈焰的黑板报
在这里插入图片描述

在大数据领域,分布式事件流处理已经成为了热门话题。目前流行的流处理引擎包括 Apache Storm、Apache Flink、Heron、Apache Kafka(Kafka Streams)和 Apache Spark(Spark Streaming)。流处理引擎被广泛讨论的一个特性——Extractly-Once,很多引擎也已经宣称支持。

然而,关于 Extractly-Once 是什么,当流处理引擎宣称支持它时,它真正意味着什么。关于 Extractly-Once 的处理语义描述也是非常容易让人误导的。在这篇文章中,我将讨论 Extractly-Once 在许多流处理引擎之间的异同,以及为什么 Extractly-Once 被描述为 Effectively-Once 会更好。我也将探索用于实现所谓 Extractly-Once 的常用技术之间的权衡。

背景

流处理简单的描述为对一系列无限连续的数据或事件的连续处理,有时也指事件处理。一个流处理或事件处理应用或多或少可以用一个有向图描述,通常是有向无环图。在这样的图中,每条边表示一个数据或事件流,每个顶点表示一个算子,该算子使用应用程序定义的逻辑来处理来自相邻边的数据或事件。有两种特殊类型的顶点——Source 和 Sink。Source 消费外部数据或事件到应用程序中,Sink 通常收集应用程序处理的结果。下图 1 描述了一个流式应用的例子:
图1 Heron 典型的处理拓扑逻辑
流处理引擎通常允许用户指定可靠的模式或处理语义,这些模式或语义表示它将为整个应用程序图中的数据处理提供哪些保证。这些保持也都是有意义的,因为你总会遇到由于网络、机器故障导致数据丢失的情况。有三种模式通常被用于描述流处理引擎应该提供的数据处理语义:

  1. at-most-once
  2. at-least-once
  3. extractly-once

下面是这些不同处理语义的松散定义:

At-most-once

这本质上是一种“尽力而为”的方式。数据或事件被保证在应用中最多被所有算子处理一次。这意味着如果在应用处理完之前数据丢失,那么没有额外的重试或重新发送。图 2 展示了这种例子:
图2 At-most-once 处理语义

At-least-once

应用中的所有算子保证数据或事件被至少处理一次。这意味着如果在应用处理完之前有事件丢失,该事件将会被从数据源重放或重新发送。因为它可以被重新发送,一个事件有时可以被处理多次,即至少一次。图 3 展示了这种例子:第一个算子处理事件失败,然后重试成功,接着第二次重试也成功了,其实第二次重试是没必要的。
图3 At-least-once 处理语义

Extractly-once

即使在发生各种故障时,流应用程序的所有算子保证事件被精确一次(Extractly-Once)地处理。

通常有两种机制用于实现 Extractly-Once 语义:

  1. 分布式快照/状态检查点
  2. 至少一次事件传输 + 消息去重

实现 Extractly-Once 的分布式快照/状态检查点方法受到了 Chandy-Lamport 分布式快照算法的启发。在这中机制中,流应用程序中每个算子的所有状态被周期性地做 checkpoint,系统中任何一处事件失败,每个算子的所有状态都会回滚到最近一次全局一致 checkpoint。在回滚期间,所有处理将被暂停。Source 也被设置成与最近的 checkpoint 对应的偏移量。整个流应用程序基本上都回到最近一致状态,然后处理从该状态重新启动。图 4 展示了这种机制的基本原理。
图4 分布式快照
图 4 中,流应用程序在 T1 时刻正常执行,并且状态做了 checkpoint。但是,在 T2 时刻,算子处理输入数据失败。此时,状态值 S = 4 已经保存到了持久化存储中,而状态值 S = 12 保存在算子的内存中。为了修复这种差异,在 T3 时刻处理状态回滚到 S = 4,并且重放流中的每个连续状态,直到最近的状态,并处理每个数据。最终的结果是有些数据被处理了多次,但是这是没问题的,因为无论多少次回滚,结果状态是一样的。

另一种实现 Extractly-Once 的方法是通过实现至少一次事件传输,以及每个算子对重复数据去重。流处理引擎利用这种方式重放失败的事件,以便事件进入用户自定义的逻辑之前,进一步尝试去除每个算子中的重复数据。这种机制要求对每个算子维护一个事务日志,以便追踪哪个事件它已经被处理过了。利用这种机制的流处理引擎有 Google MillWheel 和 Apache Kafka Stream。图 5 展示了该机制的要点:
图5 至少一次传输加去重

Extractly-Once 是真正的 Extractly-Once?

让我们重新考察下 Extractly-Once 语义对于最终用户的保证。Extractly-Once 对于只处理一次的描述让人误导。

有些人可能认为 Extractly-Once 表示保证了流中的每个事件仅仅会被处理一次。事实上,没有任何流处理引擎可以保证精确一次地处理。在面对任意失败时,保证每个算子中用户自定义逻辑对于每个事件只处理一次是不可能的,因为用户代码被部分执行是经常存在的。

考虑这种场景,你有一个执行 Map 操作的流处理算子,它打印输入事件的 ID 并且原样返回该事件。如下面伪代码所示:

Map (Event event) {
    Print "Event ID: " + event.getId()
    Return event
}

每个事件有一个 GUID(全局唯一 ID)。如果用户逻辑的精确一次地执行可以被保证,那么事件 ID 将会只被打印一次。但是,这是不可能保证的,因为在执行用户定义逻辑的任何时间和任何点都可能发生故障。流处理引擎不能自行确定执行用户自定义逻辑的时间点。因此,任何用户自定义的逻辑都不能保证被只执行一次。这也暗示着用户自定义逻辑中实现像数据库写这样的外部操作也不能保证被只执行一次。这种操作仍然需要以幂等的方式实现。

当流处理引擎声明 Extractly-Once 语义时,它能够保证什么呢?如果用户逻辑不能被保证只执行一次,那什么只执行一次呢?当流处理引擎声明 Extractly-Once 语义时,它们实际上在说可以保证对引擎管理的状态的更新只提交一次到持久性后端存储。

以上两种机制都使用持久后端存储作为真实性的来源,它可以保存每个操作符的状态并自动向其提交更新。对于机制 1(分布式快照/状态检查点),持久后端状态被用于存储流应用程序全局一致性状态检查点(每个算子的检查点状态)。对于机制 2(至少一次事件传输 + 消息去重),持久后端状态被用于每个算子的状态和追踪每个算子已经完全处理的事件的事务日志。

提交状态或对作为真实来源的持久后端应用更新可以被描述为恰好发生一次。然而,如上所述,计算状态更新/更改,即处理在事件上执行任意用户定义逻辑,如果发生故障,则可能发生不止一次。换言之,事件的处理可以发生多次,但是处理的效果只会在持久化后端状态存储中反映一次。因此,我们认为有效一次(Effectively-Once)是这种处理语义的更好描述。

分布式快照与至少一次事件传递和数据去重

从语义角度看,分布式快照和至少一次事件传输 + 去重机制都提供了一样的保证,由于两种机制的实现不同,它们存在显著的性能差异。

机制 1 (分布式快照/状态检查点)的性能开销最小。因为流处理引擎本质上是往流应用程序中发送一些特殊事件和常规事件,而状态检查点可以异步地在后台执行。然而,对于大型流处理应用,故障也会发送的更频繁,导致流处理引擎需要暂停应用程序,同时回滚所有算子的状态,这反过来又会影响性能。越大型的流处理应用程序,故障发生的越频繁,反过来,流应用程序性能影响也就越大。但是,这种非侵入式机制,运行时需要的额外资源的影响很小。

机制 2(至少一次传输 + 去重)可能要求更多的资源,尤其是存储。使用这种机制,流处理引擎将需要能够跟踪由算子的每个实例完全处理过的每个元组,以执行数据去重,以及为每个事件执行重复数据删除。这相当于需要追踪大量的数据,尤其是如果流应用程序规模大或者有很多应用程序在运行的时候。在每个算子上执行数据去重的每个事件也会带来性能开销。使用这种机制,流应用程序的性能不太可能会被应用程序的大小所影响。使用机制 1,如果在任意算子发生任何故障,都需要全局暂停并且回滚状态;使用机制 2,失败的影响更加局部化。当一个算子发生故障,可能尚未处理完成的事件仅需要上游数据源重放或重新发送即可。性能的影响被隔离在流应用程序失败发生的地方,几乎不会应用程序中的其他算子的性能造成影响。从性能角度来看,这两种机制的优缺点如下。

优点缺点
分布式快照/状态检查点较小的性能和资源开销。1. 故障恢复对性能影响较大;2. 拓扑越大,对性能的潜在影响越大。
至少一次传输 + 去重1. 故障对性能的影响是局部的;2. 故障的影响不会随拓扑的大小而增加。1. 可能需要大量的存储和基础设施来支持;2. 每个算子的每个事件的性能开销。

虽然从理论上讲,分布式快照和至少一次事件传递加重复数据删除机制之间存在差异,但两者都可以简化为至少一次处理加幂等性。对于这两种机制,当发生故障时(至少实现一次),事件将被重放/重新发送,并且通过状态回滚或事件去重,算子在更新内部管理状态时本质上是幂等的。

总结

通过本文,我希望你能相信 Extractly-Once 术语是非常具有误导性的。Extractly-Once 语义真正的意思是流处理引擎管理的算子状态的不同更新只反映一次。Extractly-Once 并不能保证事件的处理。如,任意用户自定逻辑的执行只发生一次。因此,我们更倾向于用 Effectively-Once 术语表示这种保证,因为处理不需要被保证只发生一次,但是流处理引擎管理的状态的影响只会反映一次。这两种机制,分布式快照和数据去重,都被用于实现 Extractly/Effectively-Once 语义。这种机制都为消息处理和状态更新提供了相同的语义保证,尽管如此,在性能上还是存在差异。本文不是为了使你相信这些机制孰优孰劣,它们各有利弊。

参考文献:

  1. Chandy, K. Mani and Leslie Lamport.Distributed snapshots: Determining global states of distributed systems. ACMTransactions on Computer Systems (TOCS) 3.1 (1985): 63-75.
  2. Akidau, Tyler, et al. MillWheel:Fault-tolerant stream processing at internet scale. Proceedings of the VLDBEndowment 6.11 (2013): 1033-1044.
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值