当触发一个RDD的action后。以count为例,调用关系例如以下:

  1. org.apache.spark.rdd.RDD#count
  2. org.apache.spark.SparkContext#runJob
  3. org.apache.spark.scheduler.DAGScheduler#runJob
  4. org.apache.spark.scheduler.DAGScheduler#submitJob
  5. org.apache.spark.scheduler.DAGSchedulerEventProcessActor#receive(JobSubmitted)
  6. org.apache.spark.scheduler.DAGScheduler#handleJobSubmitted

当中步骤五的DAGSchedulerEventProcessActor是DAGScheduler 的与外部交互的接口代理,DAGScheduler在创建时会创建名字为eventProcessActor的actor。这个actor的作用看它的实现就一目了然了:

  /**
* The main event loop of the DAG scheduler.
*/
def receive = {
case JobSubmitted(jobId, rdd, func, partitions, allowLocal, callSite, listener, properties) =>
dagScheduler.handleJobSubmitted(jobId, rdd, func, partitions, allowLocal, callSite,
listener, properties) // 提交job。来自与RDD->SparkContext->DAGScheduler的消息。 之所以在这须要在这里中转一下,是为了模块功能的一致性。 case StageCancelled(stageId) => // 消息源org.apache.spark.ui.jobs.JobProgressTab,在GUI上显示一个SparkContext的Job的执行状态。
// 用户能够cancel一个Stage。会通过SparkContext->DAGScheduler 传递到这里。
dagScheduler.handleStageCancellation(stageId) case JobCancelled(jobId) => // 来自于org.apache.spark.scheduler.JobWaiter的消息。取消一个Job
dagScheduler.handleJobCancellation(jobId) case JobGroupCancelled(groupId) => // 取消整个Job Group
dagScheduler.handleJobGroupCancelled(groupId) case AllJobsCancelled => //取消全部Job
dagScheduler.doCancelAllJobs() case ExecutorAdded(execId, host) => // TaskScheduler得到一个Executor被加入的消息。详细来自org.apache.spark.scheduler.TaskSchedulerImpl.resourceOffers
dagScheduler.handleExecutorAdded(execId, host) case ExecutorLost(execId) => //来自TaskScheduler
dagScheduler.handleExecutorLost(execId) case BeginEvent(task, taskInfo) => // 来自TaskScheduler
dagScheduler.handleBeginEvent(task, taskInfo) case GettingResultEvent(taskInfo) => //处理获得TaskResult信息的消息
dagScheduler.handleGetTaskResult(taskInfo) case completion @ CompletionEvent(task, reason, _, _, taskInfo, taskMetrics) => //来自TaskScheduler,报告task是完毕或者失败
dagScheduler.handleTaskCompletion(completion) case TaskSetFailed(taskSet, reason) => //来自TaskScheduler,要么TaskSet失败次数超过阈值或者因为Job Cancel。
dagScheduler.handleTaskSetFailed(taskSet, reason) case ResubmitFailedStages => //当一个Stage处理失败时,重试。来自org.apache.spark.scheduler.DAGScheduler.handleTaskCompletion
dagScheduler.resubmitFailedStages()
}

总结一下org.apache.spark.scheduler.DAGSchedulerEventProcessActor的作用:能够把他理解成DAGScheduler的对外的功能接口。

它对外隐藏了自己内部实现的细节。也更易于理解其逻辑;也减少了维护成本,将DAGScheduler的比較复杂功能接口化。

handleJobSubmitted

org.apache.spark.scheduler.DAGScheduler#handleJobSubmitted首先会依据RDD创建finalStage。

finalStage,顾名思义,就是最后的那个Stage。

然后创建job,最后提交。

提交的job假设满足一下条件。那么它将以本地模式执行:

1)spark.localExecution.enabled设置为true  而且 2)用户程序显式指定能够本地执行 而且 3)finalStage的没有父Stage 而且 4)仅有一个partition

3)和 4)的话主要为了任务能够高速执行。假设有多个stage或者多个partition的话,本地执行可能会因为本机的计算资源的问题而影响任务的计算速度。

要理解什么是Stage。首先要搞明确什么是Task。Task是在集群上执行的基本单位。一个Task负责处理RDD的一个partition。RDD的多个patition会分别由不同的Task去处理。

当然了这些Task的处理逻辑全然是一致的。

这一组Task就组成了一个Stage。有两种Task:

  1. org.apache.spark.scheduler.ShuffleMapTask
  2. org.apache.spark.scheduler.ResultTask

ShuffleMapTask依据Task的partitioner将计算结果放到不同的bucket中。而ResultTask将计算结果发送回Driver Application。一个Job包括了多个Stage,而Stage是由一组全然同样的Task组成的。

最后的Stage包括了一组ResultTask。

在用户触发了一个action后,比方count,collect。SparkContext会通过runJob的函数開始进行任务提交。最后会通过DAG的event processor 传递到DAGScheduler本身的handleJobSubmitted,它首先会划分Stage,提交Stage,提交Task。

至此,Task就開始在执行在集群上了。

一个Stage的開始就是从外部存储或者shuffle结果中读取数据;一个Stage的结束就是因为发生shuffle或者生成结果时。

创建finalStage

handleJobSubmitted 通过调用newStage来创建finalStage:

finalStage = newStage(finalRDD, partitions.size, None, jobId, callSite)

创建一个result stage。或者说finalStage,是通过调用org.apache.spark.scheduler.DAGScheduler#newStage完毕的。而创建一个shuffle stage,须要通过调用org.apache.spark.scheduler.DAGScheduler#newOrUsedStage。

private def newStage(
rdd: RDD[_],
numTasks: Int,
shuffleDep: Option[ShuffleDependency[_, _, _]],
jobId: Int,
callSite: CallSite)
: Stage =
{
val id = nextStageId.getAndIncrement()
val stage =
new Stage(id, rdd, numTasks, shuffleDep, getParentStages(rdd, jobId), jobId, callSite)
stageIdToStage(id) = stage
updateJobIdStageIdMaps(jobId, stage)
stage
}

对于result 的final stage来说。传入的shuffleDep是None。

我们知道。RDD通过org.apache.spark.rdd.RDD#getDependencies能够获得它依赖的parent RDD。

而Stage也可能会有parent Stage。

看一个RDD论文的Stage划分吧:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYW56aHNvZnQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

一个stage的边界。输入是外部的存储或者一个stage shuffle的结果;输入则是Job的结果(result task相应的stage)或者shuffle的结果。

上图的话stage3的输入则是RDD A和RDD F shuffle的结果。

而A和F因为到B和G须要shuffle,因此须要划分到不同的stage。

从源代码实现的角度来看。通过触发action也就是最后一个RDD创建final stage(上图的stage 3),我们注意到new Stage的第五个參数就是该Stage的parent Stage:通过rdd和job id获取:

// 生成rdd的parent Stage。没遇到一个ShuffleDependency。就会生成一个Stage
private def getParentStages(rdd: RDD[_], jobId: Int): List[Stage] = {
val parents = new HashSet[Stage] //存储parent stage
val visited = new HashSet[RDD[_]] //存储已经被訪问到得RDD
// We are manually maintaining a stack here to prevent StackOverflowError
// caused by recursively visiting // 存储须要被处理的RDD。Stack中得RDD都须要被处理。 val waitingForVisit = new Stack[RDD[_]]
def visit(r: RDD[_]) {
if (!visited(r)) {
visited += r
// Kind of ugly: need to register RDDs with the cache here since
// we can't do it in its constructor because # of partitions is unknown
for (dep <- r.dependencies) {
dep match {
case shufDep: ShuffleDependency[_, _, _] => // 在ShuffleDependency时须要生成新的stage
parents += getShuffleMapStage(shufDep, jobId)
case _ =>
waitingForVisit.push(dep.rdd) //不是ShuffleDependency,那么就属于同一个Stage
}
}
}
}
waitingForVisit.push(rdd) // 输入的rdd作为第一个须要处理的RDD。 然后从该rdd開始。顺序訪问其parent rdd
while (!waitingForVisit.isEmpty) { //仅仅要stack不为空。则一直处理。
visit(waitingForVisit.pop()) //每次visit假设遇到了ShuffleDependency,那么就会形成一个Stage,否则这些RDD属于同一个Stage
}
parents.toList
}

生成了finalStage后。就须要提交Stage了。

  // 提交Stage,假设有parent Stage没有提交,那么递归提交它。
private def submitStage(stage: Stage) {
val jobId = activeJobForStage(stage)
if (jobId.isDefined) {
logDebug("submitStage(" + stage + ")")
// 假设当前stage不在等待其parent stage的返回。而且 不在执行的状态, 而且 没有已经失败(失败会有重试机制,不会通过这里再次提交)
if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {
val missing = getMissingParentStages(stage).sortBy(_.id)
logDebug("missing: " + missing)
if (missing == Nil) { // 假设全部的parent stage都已经完毕,那么提交该stage所包括的task
logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents")
submitMissingTasks(stage, jobId.get)
} else {
for (parent <- missing) { // 有parent stage为完毕,则递归提交它
submitStage(parent)
}
waitingStages += stage
}
}
} else {
abortStage(stage, "No active job for stage " + stage.id)
}
}

DAGScheduler将Stage划分完毕后,提交实际上是通过把Stage转换为TaskSet,然后通过TaskScheduler将计算任务终于提交到集群。

其所在的位置例如以下图所看到的。

接下来,将分析Stage是怎样转换为TaskSet,并终于提交到Executor去执行的。

BTW。近期工作太忙了,基本上到家洗漱完都要10点多。

也再没有精力去进行源代码解析了。幸运的是周末不用加班。因此以后的博文更新都要集中在周末了。加油。

Spark技术内幕:Stage划分及提交源代码分析的更多相关文章

  1. Spark技术内幕: Task向Executor提交的源代码解析

    在上文<Spark技术内幕:Stage划分及提交源代码分析>中,我们分析了Stage的生成和提交.可是Stage的提交,仅仅是DAGScheduler完毕了对DAG的划分,生成了一个计算拓 ...

  2. Spark技术内幕: Task向Executor提交的源码解析

    在上文<Spark技术内幕:Stage划分及提交源码分析>中,我们分析了Stage的生成和提交.但是Stage的提交,只是DAGScheduler完成了对DAG的划分,生成了一个计算拓扑, ...

  3. Spark技术内幕:Stage划分及提交源码分析

    http://blog.csdn.net/anzhsoft/article/details/39859463 当触发一个RDD的action后,以count为例,调用关系如下: org.apache. ...

  4. Spark技术内幕:Master的故障恢复

    Spark技术内幕:Master基于ZooKeeper的High Availability(HA)源码实现  详细阐述了使用ZK实现的Master的HA,那么Master是如何快速故障恢复的呢? 处于 ...

  5. Spark技术内幕:Client,Master和Worker 通信源代码解析

    Spark的Cluster Manager能够有几种部署模式: Standlone Mesos YARN EC2 Local 在向集群提交计算任务后,系统的运算模型就是Driver Program定义 ...

  6. Spark技术内幕:Master基于ZooKeeper的High Availability(HA)源代码实现

    假设Spark的部署方式选择Standalone,一个採用Master/Slaves的典型架构,那么Master是有SPOF(单点故障,Single Point of Failure).Spark能够 ...

  7. Spark技术内幕:究竟什么是RDD

    RDD是Spark最基本,也是最根本的数据抽象.http://www.cs.berkeley.edu/~matei/papers/2012/nsdi_spark.pdf 是关于RDD的论文.如果觉得英 ...

  8. Spark技术内幕:Client,Master和Worker 通信源码解析

    http://blog.csdn.net/anzhsoft/article/details/30802603 Spark的Cluster Manager可以有几种部署模式: Standlone Mes ...

  9. 我的第一本著作:Spark技术内幕上市!

    现在各大网站销售中! 京东:http://item.jd.com/11770787.html 当当:http://product.dangdang.com/23776595.html 亚马逊:http ...

随机推荐

  1. 第四天学习内容 if switch for 的练习

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...

  2. JavaScript移除数组元素

    //数组移除长度方法 var array=[]; array[0]="张三"; array[1]="李四"; array[2]="王五"; ...

  3. hdu 4687 带花树匹配

    其实吧,思路挺简单的,只不过昨天刚学,还有一些东西不太了解,然后就23333333... 吃晚饭回来就A了,我是有多傻啊,这么题都A不掉,不能忍啊... 我们可以先求出哪些边是可能存在于最大匹配中的, ...

  4. Swift - 本地数据的保存与加载(使用NSCoder将对象保存到.plist文件)

    下面通过一个例子将联系人数据保存到沙盒的“documents”目录中.(联系人是一个数组集合,内部为自定义对象). 功能如下: 1,点击“保存”将联系人存入userList.plist文件中 2,点击 ...

  5. Swift - 歌曲播放样例(附带播放时间和播放进度)

    使用MPMoviePlayerController我们可以是进行音乐播放,下面是一个播放器样例.同时,播放时还可以显示播放时间和播放进度. 1 2 3 4 5 6 7 8 9 10 11 12 13 ...

  6. isHiden和isVisible的区别(isVisible更可靠)

    之前一直对isHiden和isVisible的区别比较模糊,都是乱用的.今天因需要仔细看了一下. 1.isHiden只是返回部件的隐藏属性,并不能表示部件当前的真实状态.比如A部件有个子部件B,而A处 ...

  7. 【ASP.NET Web API教程】5.5 ASP.NET Web API中的HTTP Cookie

    原文:[ASP.NET Web API教程]5.5 ASP.NET Web API中的HTTP Cookie 5.5 HTTP Cookies in ASP.NET Web API 5.5 ASP.N ...

  8. JAVA中IO和NIO的详解分析,内容来自网络和自己总结

    用一个例子来阐释: 一辆客车上有10个乘客,他们的目的地各不相同,当没有售票员的时候,司机就需要不断的询问每一站是否有乘客需要下车,需要则停下,不需要则继续开车,这种就是阻塞的方式. 当有售票员的时候 ...

  9. 一句话解释JVM中空间分配担保的问题

    先解释YGC: 当对象生成在EDEN区失败时,出发一次YGC,先扫描EDEN区中的存活对象,进入S0区,S0放不下的进入OLD区,再扫描S1区,若存活次数超过阀值则进入OLD区,其它进入S0区,然后S ...

  10. 写给C语言新手的话

    首先声明啊,写这个是因为一些加我QQ的朋友问我学习经验,我才写的. 另外,如果是二级党,那么请用谭浩强老师的书.然后你就可以不用看了.倒不是有偏见,而是我写的这个东西,根本不是为了考试,而是为了和新手 ...