为什么对 Spark Streaming 微批处理(使用 kafka 作为源时)有这么多批评?

Why so much criticism around Spark Streaming micro-batch (when using kafka as source)?

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

我的意思是:很多批评都围绕着 Spark Streaming 微批处理架构。而且,通常,人们说 Kafka Streams 是一个真正的 'real-time' 工具,因为它一个接一个地处理事件。

它确实一个接一个地处理事件,但据我了解,它使用(几乎所有其他 library/framework)消费者 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**
        }
}

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

  1. 可能有更高的延迟,因为它的微批次最小间隔将延迟限制在最多 100 毫秒(参见 Spark 结构化流媒体文档);
  2. 按组处理记录(作为 RDD 的 DStream 或作为结构化流中的数据帧)。

但是:

  1. 可以在 Spark 中逐条处理记录 - 只需循环 RDDs/Rows
  2. Kafka Streams 实际上会轮询成批记录,但会逐个处理它们,因为它在底层实现了消费者API。

澄清一下,我不是从 'fan-side' 提问(因此,这是一个意见问题),恰恰相反,我真的想从技术上理解它,以便理解语义在流媒体生态系统中。

感谢这件事中的每一条信息。

免责声明:我参与了 Apache Storm(众所周知,它是一个处理“逐条记录”的流式框架,尽管也有三叉戟 API),现在参与了 Apache Spark( "微批").

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

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

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

在流式框架中做“微批”,他们必须为每个微批确定“批”的边界。在 Spark 中,计划(例如,该批次将从源和进程中读取多少条记录)通常由驱动程序端完成,任务是根据确定的批次进行物理计划的。这种方法给最终用户一个主要的功课——“合适”的批次大小是多少才能达到他们的目标throughput/latency。批次太小会导致吞吐量不佳,因为计划批次需要大量成本(很大程度上取决于来源)。太大的批次会导致糟糕的延迟。此外,“阶段”的概念适用于批处理工作负载(我看到 Flink 在其批处理工作负载中采用阶段)而不适合流式工作负载,因为这意味着一些任务应该等待其他任务的“完成” , 没有管道。

当然,我不认为这样的批评意味着微批处理“无法使用”。当您的实际工作负载可以忍受几分钟(甚至几十分钟)的延迟时,您真的需要打扰延迟吗?可能没有。您需要关注学习曲线的成本(很可能是 Spark only 与 Spark & other,但 Kafka stream 或 Flink only 肯定是可能的。)和维护。此外,如果您的工作负载需要聚合(可能使用 windowing),那么框架对延迟的限制就不那么重要了,因为您可能会将 window 大小设置为 minutes/hours.

微批处理也有好处 - 如果有大量空闲,资源 运行 空闲任务就会被浪费,这适用于“记录到记录”流式处理框架。它还允许对特定的微批次进行批量操作,这在流媒体上是不可能的。 (不过你应该记住它只适用于“当前”批次。)

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

编辑:如果您的工作负载以“端到端恰好一次”为目标,即使对于“逐条记录”流式传输框架,延迟也会绑定到检查点间隔。 checkpoint之间的记录组成了一个batch,所以checkpoint interval对你来说是一个新的大作业。

编辑 2:

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

与自然带来的延迟相比,micro-batch和record-by-record之间的输出延迟不会很大(即使是micro-batch在某些极端情况下也可以达到亚秒级延迟) windowing.

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

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

我不知道关于 Kafka 流的细节,所以我的解释不会基于 Kafka 流的工作原理。那将是你的作业。

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

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

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

  1. 支持幂等更新
  2. 支持交易更新

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

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

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

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

  • 逐条记录:源不断地抓取记录并发送给下游运营商。 Source 不需要等待下游操作员完成先前的记录。在最近的流媒体框架中,非混洗运算符会在一个任务中一起处理——对于这种情况,这里的下游运算符在技术上意味着有一个下游运算符需要“混洗”。
  • Micro-batch:引擎规划新的micro-batch(源的offset范围等),并为该micro-batch启动任务。在每个微批中,它的行为与批处理相似。