资源调度:
(1)executor默认在集群中分散启动,可通过参数配置集中在某个work启动,不过分散启动有利于数据本地化。
(2)如果spark-submit提交任务时,如果不指定--executor-cores,则spark会在每个work中启动一个executor并消耗掉work中的所有core和1G的内存。
(3)如果只设置--executor-cores而不设置--total-executor-cores则会,每启动一个executor耗费--executor-cores配置的核,而且也会消耗掉所有work中的core,直到不能在启动executor为止。
(4)只有指定--executor-cores并且指定--total-executor-cores,才会限定住executor的个数和每个executor需要的core个数。
任务调度:
按照action算子划分job,每个job由DAGSchedule划分stage,由finalRdd开始由后向前递归划分stage,划分stage的关键方法是submitStage:
private def submitStage(stage: Stage) { val jobId = activeJobForStage(stage) if (jobId.isDefined) { logDebug("submitStage(" + stage + ")") if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) { val missing = getMissingParentStages(stage).sortBy(_.id) logDebug("missing: " + missing) if (missing.isEmpty) { logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents") submitMissingTasks(stage, jobId.get) } else { for (parent <- missing) { submitStage(parent) } waitingStages += stage } } } else { abortStage(stage, "No active job for stage " + stage.id, None) } }
每次找到窄依赖会进行压栈操作,当所有窄依赖都找到完毕以后,会返回missing集合,missing中存储的是还没有向内继续切割的不完整的stage。getMissingParentStages方法如下:
private def getMissingParentStages(stage: Stage): List[Stage] = { val missing = new HashSet[Stage] val visited = new HashSet[RDD[_]] // We are manually maintaining a stack here to prevent StackOverflowError // caused by recursively visiting val waitingForVisit = new Stack[RDD[_]] def visit(rdd: RDD[_]) { if (!visited(rdd)) { visited += rdd val rddHasUncachedPartitions = getCacheLocs(rdd).contains(Nil) if (rddHasUncachedPartitions) { for (dep <- rdd.dependencies) { dep match { case shufDep: ShuffleDependency[_, _, _] => val mapStage = getShuffleMapStage(shufDep, stage.firstJobId) if (!mapStage.isAvailable) { missing += mapStage } case narrowDep: NarrowDependency[_] => waitingForVisit.push(narrowDep.rdd) } } } } } waitingForVisit.push(stage.rdd) while (waitingForVisit.nonEmpty) { visit(waitingForVisit.pop()) } missing.toList }
当stage划分完毕以后,会将每个stage封装成Task,并最后将Task集合发送给executor去执行。
ps:优化
(1)consolidationFile参数打开,这样只在shuffle的第一个并行阶段创建buffer和对应的file,后面的executor执行Task不在继续创建文件,减少了文件创建的代价。
(2)上游ShuffleMapTask输出文件以后,下游ResultTask拉取数据时,可调整每次拉取数据的多少参数,这个要视情况而定,如果吗茫然设置过大则可能发生OOM的风险。
(3)可调整数据本地化级别参数,调整本地化级别参数的等待时间,如果为了本地化而等待时间过长,拖慢整个spark作业,则也不值得,所以要全横设置。
(4)多次重复使用的数据要进行persist存储,以免下次计算。
(5)如果多次使用某些固定数据时,比如字典数据,则需要使用Broadcast,这样每个executor进程中存在一份,而不会每个Task中复制一份。
(6)使用Kryo序列化框架,更快的序列化更好的压缩