为什么对 Spark Streaming 微批次(使用 kafka 作为源时)有如此多的批评?

YFl*_*YFl 3 apache-spark spark-structured-streaming

由于任何 Kafka Consumer 实际上都是批量消费,为什么与 Kafka Streams(将自己推销为真正的流媒体)相比,Spark Streaming 微批量(当使用 Kafka 作为源时)有如此多的批评?

我的意思是:很多批评都集中在 Spark Streaming 微批处理架构上。通常,人们说 Kafka Streams 是一个真正的“实时”工具,因为它逐一处理事件。

它确实会一一处理事件,但是根据我的理解,它使用(与几乎所有其他库/框架一样)消费者 API。Consumer API 批量轮询主题,以减轻网络负担(间隔可配置)。因此,消费者会做类似的事情:

while (true) {
        ConsumerRecords<String, String> records = consumer.poll(100);

        ///// PROCESS A **BATCH** OF RECORDS
        for (ConsumerRecord<String, String> record : records) {

            ///// PROCESS **ONE-BY-ONE**
        }
}
Run Code Online (Sandbox Code Playgroud)

因此,尽管 Spark 的说法是正确的:

  1. 由于其微批量最小间隔将延迟限制为最多 100 毫秒,因此可能具有更高的延迟(请参阅 Spark 结构化流 DOC);
  2. 成组处理记录(作为 RDD 的 DStream 或结构化流中的 DataFrame)。

但:

  1. 可以在 Spark 中逐一处理记录 - 只需循环 RDD/行
  2. Kafka Streams 实际上会轮询一批记录,但会逐一处理它们,因为它在底层实现了 Consumer API。

需要澄清的是,我并不是从“粉丝方面”提出问题(因此,这是一个意见问题),恰恰相反,我真的试图从技术上理解它,以便理解流媒体生态系统中的语义。

欣赏这件事上的每一条信息。

Jun*_*Lim 9

免责声明:我曾参与过 Apache Storm(众所周知,它是一个处理“逐记录”的流框架,尽管也有 trident API),现在参与了 Apache Spark(“微批量”)。

流媒体技术的主要关注点之一是“吞吐量与延迟”。从延迟的角度来看,“逐条记录”处理显然是赢家,但“一件一件做”的成本是巨大的,每一件小事都会变成巨大的开销。(考虑到系统的目标是每秒处理一百万条记录,那么处理上的任何额外开销都会多路复用一百万条。)实际上,也有相反的批评,与“微记录”相比,“按记录读取”的吞吐量较差。 -批”。为了解决这个问题,流媒体框架在其“内部”逻辑中添加了批处理,但以减少延迟的方式进行。(比如配置批次的大小,以及强制刷新批次的超时)

我认为两者之间的主要区别在于任务是否“连续”运行以及它们是否组成“管道”。

在流框架中,“逐条记录”,当应用程序启动时,所有必要的任务都在物理上计划并一起启动,并且除非应用程序终止,否则它们永远不会终止。源任务不断地将记录推送到下游任务,下游任务处理它们并推送到下一个下游任务。这是以管道方式完成的。除非没有要推送的记录,否则源不会停止推送记录。(有背压和分布式检查点,但让我们抛开细节并关注概念。)

在流框架中进行“微批次”时,它们必须决定每个微批次的“批次”边界。在 Spark 中,规划(例如,该批次将从源和进程中读取多少记录)通常由驱动程序端完成,并且任务是根据决定的批次进行物理规划的。这种方法给最终用户带来了一个重要的作业——实现他们目标吞吐量/延迟的“适当”批量大小是多少。批次太小会导致吞吐量下降,因为规划批次需要不小的成本(很大程度上取决于来源)。太大的批次会导致严重的延迟。此外,“阶段”的概念适用于批处理工作负载(我看到 Flink 在其批处理工作负载中采用了阶段),但对于流工作负载来说并不理想,因为这意味着某些任务应该等待其他任务的“完成” ,无管道。

当然,我不认为这样的批评意味着微批次“不可用”。当您的实际工作负载可以容忍几分钟(甚至几十分钟)的延迟时,您真的需要担心延迟吗?可能不会。您需要关心学习曲线的成本(很可能仅使用 Spark 与 Spark 等,但肯定可以仅使用 Kafka 流或仅使用 Flink。)和维护成本。此外,如果您的工作负载需要聚合(可能需要窗口),那么框架的延迟限制就不那么重要,因为您可能会将窗口大小设置为分钟/小时。

微批量也有优点 - 如果有大量空闲,运行空闲任务的资源就会被浪费,这适用于“记录到记录”流框架。它还允许对特定的微批次进行批处理操作,这在流式传输中是不可能的。(尽管您应该记住它仅适用于“当前”批次。)

我认为没有灵丹妙药——Spark 一直领先于“批处理工作负载”,因为它最初是为了处理 MapReduce 的问题,因此整体架构针对批处理工作负载进行了优化。其他流框架从“流原生”开始,因此应该在流工作负载上具有优势,但在批处理工作负载上不太理想。统一批处理和流处理是一种新趋势,有时一个(或几个)框架可能会为两种工作负载提供最佳性能,但我不确定现在是时候。

编辑:如果您的工作负载目标是“端到端一次”,则即使对于“逐条记录”流框架,延迟也与检查点间隔相关。检查点之间的记录组成了一种批次,因此检查点间隔将是您新的主要作业。

编辑2:

Q1) 为什么 Windows 聚合会让我更少担心延迟?也许人们真的想足够快地更新有状态操作。

与窗口性质带来的延迟相比,微批次和逐条记录之间的输出延迟不会很大(即使微批次在某些极端情况下也可以实现亚秒级延迟)。

但是,是的,我假设只有当窗口过期时才会发生发射(结构化流中的“追加”模式)。如果您想在窗口发生变化时发出所有更新,那么是的,延迟方面仍然存在差异。

Q2)为什么语义在这种权衡中很重要?例如,听起来它与 Kafka-Streams 在配置恰好一次时减少提交间隔有关。也许您的意思是,为了获得更好的语义,检查点可能会逐一增加开销,然后影响延迟?

我不知道有关 Kafka 流的详细信息,因此我的解释不会基于 Kafka 流的工作原理。那将是你的作业。

如果您正确地阅读了我的答案,您也同意流框架不会对每个记录执行检查点 - 开销将是巨大的。也就是说,两个检查点之间的记录将是同一组(某种批次),当发生故障时必须重新处理。

如果有状态恰好一次(有状态操作恰好一次,但输出至少一次)对于您的应用程序来说足够了,您的应用程序可以将输出写入接收器并立即提交,以便输出的读者可以立即读取它们。延迟不会受到检查点间隔的影响。

顺便说一句,有两种方法可以实现端到端一次(尤其是接收端):

  1. 支持幂等更新
  2. 支持事务更新

情况 1) 立即写入输出,因此不会通过语义影响延迟(类似于至少一次),但存储应该能够处理 upsert,并且在发生故障时会看到“部分写入”,因此您的阅读器应用程序应该容忍它。

情况 2) 写入输出,但直到检查点发生才提交它们。流框架将尝试确保仅当检查点成功并且组中没有失败时才提交和公开输出。有多种方法可以使分布式写入成为事务性的(2PC,协调器执行“原子重命名”,协调器写入已写入的文件任务列表等),但无论如何,读者都无法看到部分写入,直到提交发生,因此检查点间隔将极大地影响输出延迟。

Q3)这并不一定能解决 Kafka 客户端轮询的一批记录的问题。

我的回答解释了一般概念,即使源在轮询请求中提供一批记录,该概念也适用。

  • 逐条记录:源不断获取记录并发送给下游算子。源不需要等待下游运算符对先前记录的完成。在最近的流框架中,非洗牌运算符将在一个任务中完全处理 - 对于这种情况,这里的下游运算符在技术上意味着有一个下游运算符需要“洗牌”。
  • 微批次:引擎规划新的微批次(源的偏移范围等)并为微批次启动任务。在每个微批次中,其行为与批处理类似。