多个分区的kafka流式传输行为

时间:2017-06-08 16:27:10

标签: apache-spark spark-streaming

我正在使用Kafka话题。本主题有3个分区。 我使用foreachRDD处理每个批处理RDD(使用processData方法处理每个RDD,最终从中创建一个DataSet)。

现在,您可以看到我有计数变量,并且我在“processData”方法中递增此计数变量以检查我已经处理了多少实际记录。 (据我所知,每个RDD都是kafka主题记录的集合,数量取决于批处理间隔大小)

现在,输出是这样的:

1 1 1 2 3 2 4 3 5 ....

这让我认为它是因为我可能有3个消费者(因为我有3个分区),而且每个都会分别调用“foreachRDD”方法,所以同一个计数被打印不止一次,因为每个消费者可能已经缓存了它的计数副本。

但是我获得的最终输出DataSet具有所有记录。

那么,Spark内部联合所有数据吗?它是如何形成联合的? 我试图了解行为,以便我可以形成我的逻辑

int count = 0;

messages.foreachRDD(new VoidFunction<JavaRDD<ConsumerRecord<K, String>>>() {
            public void call(JavaRDD<ConsumerRecord<K, V>> rdd) {
                System.out.println("NUmber of elements in RDD : "+ rdd.count());

                List<Row> rows = rdd.map(record -> processData(record))
                        .reduce((rows1, rows2) -> {
                            rows1.addAll(rows2);
                            return rows1;
                        });

                StructType schema = DataTypes.createStructType(fields);
                Dataset ds = ss.createDataFrame(rows, schema);
                ds.createOrReplaceTempView("trades");                
                ds.show();
            }
        });

1 个答案:

答案 0 :(得分:1)

这些假设并不完全准确。 foreachRDD是Spark Streaming中所谓的output operations之一。 output operations的功能是按batch interval指定的间隔安排提供的闭包。该闭包中的代码在spark驱动程序上每batch interval执行一次。未在群集中分发。

特别是,foreachRDD是一个通用output operation,它提供对DStream中底层RDD的访问。应用于该RDD的操作将在Spark群集上执行。

因此,回到原始问题的代码,foreachRDD闭包中的代码(例如System.out.println("NUmber of elements in RDD : "+ rdd.count());)在驱动程序上执行。这也是我们可以在控制台中看到输出的原因。请注意,此rdd.count()中的print将触发群集上的count RDD,因此count是一个向驱动程序返回值的分布式操作,然后 - 在驱动程序上 - print操作发生。

现在转变了RDD:

rdd.map(record -> processData(record))

正如我们所提到的,应用于RDD的操作将在群集上执行。并且执行将在Spark执行模型之后执行;也就是说,转换被组合成阶段并应用于基础数据集的每个分区。鉴于我们正在处理3个kafka主题,我们将在Spark中有3个相应的分区。因此,processData将对每个分区应用一次。

  

那么,Spark内部联合所有数据吗?它如何弄清楚要结合什么?

与Spark Streaming的输出操作相同,我们有针对Spark的操作。操作可能会对数据应用操作并将结果提供给驱动程序。最简单的操作是collect,它将完整的数据集带给驱动程序,存在它可能不适合内存的风险。其他常见操作count汇总了数据集中的记录数,并将单个数字返回给驱动程序。

在上面的代码中,我们使用reduce,这也是一个应用提供的函数并将结果数据带到驱动程序的操作。这是在问题中表达的“内部联合所有数据”的行动的使用。在reduce表达式中,我们实际上收集了分发到单个本地集合中的所有数据。这相当于:rdd.map(record -> processData(record)).collect()

如果打算创建数据集,我们应该首先避免将所有数据“移动”到驱动程序。

更好的方法是:

val rows = rdd.map(record -> processData(record))
val df = ss.createDataFrame(rows, schema);
...

在这种情况下,所有分区的数据将保留在它们所在的执行程序的本地。

请注意,应避免将数据移至驱动程序。它很慢并且在大型数据集的情况下可能会使作业崩溃,因为驱动程序通常无法保存群集中的所有可用数据。