热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

我们谈论的Exactlyonce到底是什么?

ExactlyonceisNOTexac

转自:https://juejin.cn/post/6844903857558913038
英文原文:https://streaml.io/blog/exactly-once

最近在看Apache pulsar这个被誉为下一代消息队列的相关的,我之前其实已经写过很多kafka,rocketmq相关的文章,本来以为消息队列的技术大体花样都差不多,但是当我看到了pulsar的确被他的一些设计给惊艳到了。这篇文章是我看pulsar的时候觉得不错的一篇英文文章,在网上找了一下译文,直接转载给大家看。

Exactly once is NOT exactly the same

分布式事件流处理正逐渐成为大数据领域中一个热门话题。著名的流处理引擎(Streaming Processing Engines, SPEs)包括Apache Storm、Apache Flink、Heron、Apache Kafka(Kafka Streams)以及Apache Spark(Spark Streaming)。流处理引擎中一个著名的且经常被广泛讨论的特征是它们的处理语义,而“exactly-once”是其中最受欢迎的,同时也有很多引擎声称它们提供“exactly-once”处理语义。
然而,围绕着“exactly-once”究竟是什么、它牵扯到什么以及当流处理引擎声称提供“exactly-once”语义时它究竟意味着什么,仍然存在着很多误解与歧义。而用来描述处理语义的“exactly-once”这一标签同样也是非常误导人的。在这篇博文当中,我将会讨论众多受欢迎的引擎所提供的“exactly-once”语义间的不同之处,以及为什么“exactly-once”更好的描述是“effective-once”。我还会讨论用来实现“exactly-once”的常用技术间的权衡(tradeoffs)

背景

流处理(streaming process),有时也被称为事件处理(event processing),可以被简洁地描述为对于一个无限的数据或事件序列的连续处理。一个流,或事件,处理应用可以或多或少地由一个有向图,通常是一个有向无环图(DAG),来表达。在这样一个图中,每条边表示一个数据或事件流,而每个顶点表示使用应用定义好的逻辑来处理来自相邻边的数据或事件的算子。其中有两种特殊的顶点,通常被称作sources与sinks。Sources消费外部数据/事件并将其注入到应用当中,而sinks通常收集由应用产生的结果。图1描述了一个流处理应用的例子。

A typical Heron processing topology

图1 一个典型的Heron处理拓扑

一个执行流/事件处理应用的流处理引擎通常允许用户制定一个可靠性模式或者处理语义,来标示引擎会为应用图的实体之间的数据处理提供什么样的保证。由于你总是会遇到网络、机器这些会导致数据丢失的故障,因而这些保证是有意义的。有三种模型/标签,at-most-once、at-least-once以及exactly-once,通常被用来描述流处理引擎应该为应用提供的数据处理语义。接下来是对这些不同的处理语义的宽泛的定义:


At-most-once

这实质上是一个“尽力而为”(best effort)的方法。数据或者事件被保证只会被应用中的所有算子最多处理一次。这就意味着对于流处理应用完全处理它之前丢失的数据,也不会有额外的重试或重传尝试。图2展示了一个相关的例子:
At-most-once processing semantics

图2 At-most-once处理语义

At-least-once

数据或事件被保证会被应用图中的所有算子都至少处理一次。这通常意味着当事件在被应用完全处理之前就丢失的话,其会被从source开始重放(replayed)或重传(retransmitted)。由于事件会被重传,那么一个事件有时就会被处理超过一次,也就是所谓的at-least-once。图3展示了一个at-least-once的例子。在这一示例中,第一个算子第一次处理一个事件时失败,之后在重试时成功,并在结果证明没有必要的第二次重试时成功。
At-least-once processing semantics

图3 At-least-once处理语义

Exactly-once

倘若发生各种故障,事件也会被确保只会被流应用中的所有算子“恰好”处理一次。拿来实现“exactly-once”的有两种受欢迎的典型机制:1. 分布式快照/状态检查点(checkpointing) 2. At-least-once的事件投递加上消息去重
用来实现“exactly-once”的分布式快照/状态检查点方法是受到了Chandy-Lamport分布式快照算法1的启发。在这种机制中,流处理应用中的每一个算子的所有状态都会周期性地checkpointed。倘若系统发生了故障,每一个算子的所有状态都会回滚到最近的全局一致的检查点处。在回滚过程中,所有的处理都会暂停。Sources也会根据最近的检查点重置到正确到offset。整个流处理应用基本上倒回到最近的一致性状态,处理也可以从这个状态重新开始。图4展示了这种机制的基本原理。

Distributed snapshot

图4 分布式快照

在图4中,流处理应用T1时在正常地工作,同时状态也被checkpointed。T2时,算子处理一个输入数据时失败了。此时,S = 4的状态已经保存到了持久化存储当中,而S = 12的状态仍然位于算子的内存当中。为了解决这个不一致,T3时processing graph倒回到S = 4的状态并“重放”流中的每一个状态直到最新的状态,并处理每一个数据。最终结果是虽然某些数据被处理了多次,但是无论执行了多少次回滚,结果状态依然是相同的。用来实现“exactly-once”的另一种方法是在每一个算子的基础上,将at-least-once的事件投递与事件去重相结合。使用这种方法的引擎会重放失败的事件以进一步尝试进行处理,并在每一个算子上,在事件进入到用户定义的逻辑之前删除重复的事件。这一机制需要为每一个算子维护一份事务日志(transaction log)来记录哪些事件已经处理过了。使用类似这一机制的引擎有Google的MillWheel2与Apache Kafka Streams。图5展示了这一机制的重点。
At-least-once delivery plus deduplication
图5 At-least-once结合去重

exactly-once确实是exactly-once吗?

现在让我们重新审视『精确一次』处理语义真正对最终用户的保证。『精确一次』这个术语在描述正好处理一次时会让人产生误导。

有些人可能认为『精确一次』描述了事件处理的保证,其中流中的每个事件只被处理一次。实际上,没有引擎能够保证正好只处理一次。在面对任意故障时,不可能保证每个算子中的用户定义逻辑在每个事件中只执行一次,因为用户代码被部分执行的可能性是永远存在的。
考虑具有流处理运算符的场景,该运算符执行打印传入事件的 ID 的映射操作,然后返回事件不变。下面的伪代码说明了这个操作:

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

每个事件都有一个 GUID (全局惟一ID)。如果用户逻辑的精确执行一次得到保证,那么事件 ID 将只输出一次。但是,这是无法保证的,因为在用户定义的逻辑的执行过程中,随时都可能发生故障。引擎无法自行确定执行用户定义的处理逻辑的时间点。因此,不能保证任意用户定义的逻辑只执行一次。这也意味着,在用户定义的逻辑中实现的外部操作(如写数据库)也不能保证只执行一次。此类操作仍然需要以幂等的方式执行。

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

上面描述的两种机制都使用持久的后端存储作为真实性的来源,可以保存每个算子的状态并自动向其提交更新。对于机制 1 (分布式快照 状态检查点),此持久后端状态用于保存流应用程序的全局一致状态检查点(每个算子的检查点状态)。对于机制 2 (至少一次事件传递加上重复数据删除),持久后端状态用于存储每个算子的状态以及每个算子的事务日志,该日志跟踪它已经完全处理的所有事件。

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

那么,当引擎声明『精确一次』处理语义时,它们能保证什么呢?如果不能保证用户逻辑只执行一次,那么什么逻辑只执行一次?当引擎声明『精确一次』处理语义时,它们实际上是在说,它们可以保证引擎管理的状态更新只提交一次到持久的后端存储

分布式快照 vs at-least-once

从语义的角度来看,分布式快照和至少一次事件传递以及重复数据删除机制都提供了相同的保证。然而,由于两种机制之间的实现差异,存在显着的性能差异。

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

机制 2(至少一次事件传递加重复数据删除)可能需要更多资源,尤其是存储。使用此机制,引擎需要能够跟踪每个算子实例已完全处理的每个元组,以执行重复数据删除,以及为每个事件执行重复数据删除本身。这意味着需要跟踪大量的数据,尤其是在流应用程序很大或者有许多应用程序在运行的情况下。执行重复数据删除的每个算子上的每个事件都会产生性能开销。但是,使用这种机制,流应用程序的性能不太可能受到应用程序大小的影响。对于机制 1,如果任何算子发生故障,则需要发生全局暂停和状态回滚;对于机制 2,失败的影响更加局部性。当在算子中发生故障时,可能尚未完全处理的事件仅从上游源重放/重传。性能影响与流应用程序中发生故障的位置是隔离的,并且对流应用程序中其他算子的性能几乎没有影响。从性能角度来看,这两种机制的优缺点如下。

分布式快照 状态检查点的优缺点:

  • 优点:
    较小的性能和资源开销

  • 缺点:
    对性能的影响较大
    拓扑越大,对性能的潜在影响越大

至少一次事件传递以及重复数据删除机制的优缺点:

  • 优点:
    故障对性能的影响是局部的
    故障的影响不一定会随着拓扑的大小而增加

  • 缺点:
    可能需要大量的存储和基础设施来支持
    每个算子的每个事件的性能开销

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

总结

在这篇博客文章中,我希望能够让你相信『精确一次』这个词是非常具有误导性的。提供『精确一次』的处理语义实际上意味着流处理引擎管理的算子状态的不同更新只反映一次。『精确一次』并不能保证事件的处理,即任意用户定义逻辑的执行,只会发生一次。我们更喜欢用『有效一次』(effectively once)这个术语来表示这种保证,因为处理不一定保证只发生一次,但是对引擎管理的状态的影响只反映一次。两种流行的机制,分布式快照和重复数据删除,被用来实现精确/有效的一次性处理语义。这两种机制为消息处理和状态更新提供了相同的语义保证,但是在性能上存在差异。这篇文章并不是要让你相信任何一种机制都优于另一种,因为它们各有利弊。



推荐阅读
  • 在本地环境中部署了两个不同版本的 Flink 集群,分别为 1.9.1 和 1.9.2。近期在尝试启动 1.9.1 版本的 Flink 任务时,遇到了 TaskExecutor 启动失败的问题。尽管 TaskManager 日志显示正常,但任务仍无法成功启动。经过详细分析,发现该问题是由 Kafka 版本不兼容引起的。通过调整 Kafka 客户端配置并升级相关依赖,最终成功解决了这一故障。 ... [详细]
  • Ihavetwomethodsofgeneratingmdistinctrandomnumbersintherange[0..n-1]我有两种方法在范围[0.n-1]中生 ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 在 Ubuntu 中遇到 Samba 服务器故障时,尝试卸载并重新安装 Samba 发现配置文件未重新生成。本文介绍了解决该问题的方法。 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 如何在Linux服务器上配置MySQL和Tomcat的开机自动启动
    在Linux服务器上部署Web项目时,通常需要确保MySQL和Tomcat服务能够随系统启动而自动运行。本文将详细介绍如何在Linux环境中配置MySQL和Tomcat的开机自启动,以确保服务的稳定性和可靠性。通过合理的配置,可以有效避免因服务未启动而导致的项目故障。 ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • Kafka 是由 Apache 软件基金会开发的高性能分布式消息系统,支持高吞吐量的发布和订阅功能,主要使用 Scala 和 Java 编写。本文将深入解析 Kafka 的安装与配置过程,为程序员提供详尽的操作指南,涵盖从环境准备到集群搭建的每一个关键步骤。 ... [详细]
  • 本文探讨了 Kafka 集群的高效部署与优化策略。首先介绍了 Kafka 的下载与安装步骤,包括从官方网站获取最新版本的压缩包并进行解压。随后详细讨论了集群配置的最佳实践,涵盖节点选择、网络优化和性能调优等方面,旨在提升系统的稳定性和处理能力。此外,还提供了常见的故障排查方法和监控方案,帮助运维人员更好地管理和维护 Kafka 集群。 ... [详细]
  • 修复一个 Bug 竟耗时两天?真的有那么复杂吗?
    修复一个 Bug 竟然耗费了两天时间?这背后究竟隐藏着怎样的复杂性?本文将深入探讨这个看似简单的 Bug 为何会如此棘手,从代码层面剖析问题根源,并分享解决过程中遇到的技术挑战和心得。 ... [详细]
  • 深入解析十大经典排序算法:动画演示、原理分析与代码实现
    本文深入探讨了十种经典的排序算法,不仅通过动画直观展示了每种算法的运行过程,还详细解析了其背后的原理与机制,并提供了相应的代码实现,帮助读者全面理解和掌握这些算法的核心要点。 ... [详细]
  • Storm集成Kakfa
    一、整合说明Storm官方对Kafka的整合分为两个版本,官方说明文档分别如下:StormKafkaIntegratio ... [详细]
  • Hudi是一种数据湖的存储格式,在Hadoop文件系统之上提供了更新数据和删除数据的能力以及流式消费变化数据的能力。应用场景近实时数据摄取Hudi支持插入、更新和删除数据的能力。您 ... [详细]
  • 如何实现canal数据同步
    Canal简介与安装Canal简介Canal是阿里巴巴开发的MySQLbinlog增量订阅&消费组件。Canal是基于MySQL二进制日志的高性能数据同步系统。Canal在阿 ... [详细]
author-avatar
Mr-long類
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有