我是Spark的新手。需要帮助了解火花的工作原理。
假设README.md
存储在3个节点的HDFS 128块中,我使用spark shell来处理它。
val textFile = sc.textFile("README.md")
val linesWithSpark = textFile.filter(line => line.contains("Spark"))
linesWithSpark.first()
在上述情况下,执行将由第3行触发。
Spark会在HDFS节点的RAM中加载完成README.md
的3次拆分,然后过滤linesWithSpark
并保留在内存中一会儿。并从linesWithSpark
(从第一次拆分)发送第一行?
或者它只会用" Spark"拉出第一行。来自HDFS节点磁盘的Split1并将其发送给驱动程序。
如果我将第二行更改为
,处理会有什么变化val linesWithSpark = textFile.filter(line => line.contains("Spark")).cache()
答案 0 :(得分:1)
让我们从一个简单的实验开始。首先让我们加载数据并检查其分布:
val textFile = sc.textFile("README.md", 2)
textFile.glom.map(_.size).collect
// Array[Int] = Array(54, 41)
我们可以怀疑简单filter
只生成一个任务:
textFile.filter(line => line.contains("Spark")).toDebugString
// String =
// (2) MapPartitionsRDD[11] at filter at <console>:30 []
// | MapPartitionsRDD[8] at textFile at <console>:27 []
// | README.md HadoopRDD[7] at textFile at <console>:27 []
现在让我们在没有cache
的情况下运行这个工作并收集一些诊断信息:
val cnt = sc.accumulator(0L, "cnt")
val linesWithSpark = textFile.filter(line => {
cnt += 1L
line.contains("Spark")
})
linesWithSpark.first()
// String = # Apache Spark
cnt.value
/// Long = 1
正如您所见,没有缓存的作业只会处理一条记录。这是因为first
作为take(1)
执行。在第一次迭代中,take
仅运行作业on a one partition并在其迭代器上使用it.take(left)
,其中left
等于1。
由于Iterators
是惰性的,我们的程序在处理第一行后立即返回。如果第一个分区没有提供所需的结果take
迭代,则每次迭代都会增加已处理分区的数量。
接下来让我们用缓存重复相同的实验:
val cacheCntCache = sc.accumulator(0L, "cacheCnt")
val linesWithSparkCached = textFile.filter(line => {
cacheCntCache += 1L
line.contains("Spark")
}).cache()
linesWithSparkCached.first()
// String = # Apache Spark
cacheCntCache.value
// Long = 54
此外,让我们检查存储信息:
sc.getRDDStorageInfo
// Array[org.apache.spark.storage.RDDInfo] = Array(
// RDD "MapPartitionsRDD" (12)
// StorageLevel: StorageLevel(false, true, false, true, 1);
// CachedPartitions: 1; TotalPartitions: 2; MemorySize: 1768.0 B;
// ExternalBlockStoreSize: 0.0 B; DiskSize: 0.0 B)
正如您所看到的,如果缓存Spark将完成处理分区并将其缓存在内存中。虽然我无法提供导致此行为的确切部分源,但它看起来像是一个合理的优化。由于分区已经加载,因此没有理由停止工作。