最简单程序的大任务规模

时间:2015-11-24 11:19:03

标签: scala apache-spark apache-spark-sql

我正在尝试使用Spark

运行最简单的程序
import org.apache.spark.{SparkContext, SparkConf}

object LargeTaskTest {

  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("DataTest").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val dat = (1 to 10000000).toList
    val data = sc.parallelize(dat).cache()
    for(i <- 1 to 100){
      println(data.reduce(_ + _))
    }
  }   
}

每次迭代后我都会收到以下错误消息:

  

WARN TaskSetManager:阶段0包含一个非常大的任务(9767   KB)。建议的最大任务大小为100 KB。

增加数据大小会增加所述任务大小。这告诉我,驱动程序正在向所有执行程序发送“dat”对象,但我不能为我的生活看到原因,因为我的RDD上的唯一操作是reduce,它基本上没有关闭。有任何想法吗 ?

2 个答案:

答案 0 :(得分:5)

因为您首先在本地创建非常大的列表,所以Spark flatMap方法尝试将此列表作为单个单元发送给Spark工作器,作为任务的一部分。因此,您收到警告信息。作为替代方案,您可以并行化一个小得多的列表,然后使用import org.apache.spark.{SparkContext, SparkConf} object LargeTaskTest extends App { val conf = new SparkConf().setAppName("DataTest").setMaster("local[*]") val sc = new SparkContext(conf) val dat = (0 to 99).toList val data = sc.parallelize(dat).cache().flatMap(i => (1 to 1000000).map(j => j * 100 + i)) println(data.count()) //100000000 println(data.reduce(_ + _)) sc.stop() } 将其分解为更大的列表。这也有利于并行创建更大的数字集。例如:

parallelize

编辑:

最终,并行化的本地集合必须被推送到执行程序。 def parallelize[T: ClassTag]( seq: Seq[T], numSlices: Int = defaultParallelism): RDD[T] = withScope { assertNotStopped() new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]()) } 方法创建ParallelCollectionRDD的实例:

numSlices

https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/SparkContext.scala#L730

ParallelCollectionRDD创建了许多等于 override def getPartitions: Array[Partition] = { val slices = ParallelCollectionRDD.slice(data, numSlices).toArray slices.indices.map(i => new ParallelCollectionPartition(id, i, slices(i))).toArray } 的分区:

numSlices

https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/rdd/ParallelCollectionRDD.scala#L96

sc.defaultParallelism默认为SparkContext.parallelize,在我的机器上是4.所以即使拆分,每个分区都包含一个非常大的列表,需要将其推送到执行程序。

@note Parallelize acts lazily包含注释ParallelCollectionRDD<option>包含注释;

  

// TODO:现在,每个分组都会发送完整的数据,即使是   后来在它被缓存的RDD链中。这可能是值得的   将数据写入DFS中的文件并在拆分中读取//   代替。

因此,当您调用reduce时,问题就会发生,因为这是将分区发送给执行程序的要点,但根本原因是您在一个非常大的列表上调用并行化。在执行器中生成大型列表是一种更好的方法,恕我直言。

答案 1 :(得分:-2)

Reduce函数将所有数据发送到一个节点。运行sc.parallelize时,数据默认分配给100个分区。要使用已经分布的数据,请使用以下内容:

data.map(el=> el%100 -> el).reduceByKey(_+_)

或者您可以在分区级别执行reduce。

data.mapPartitions(p => Iterator(p.reduce(_ + _))).reduce(_ + _)

或只使用sum:)