前言:
通过前面部分内容,我们知道DAGScheduler会根基RDD的计算逻辑,将DAG划分为不同的Stage,每个Stage可以并发执行一组逻辑完全相同的Task,只是分布作用于不同数据集上面。
现在从一个简单的RDD count为例,来看一下Spark的内部实现原理。
1、SparkContext#runJob
def count(): Long = sc.runJob(this, Utils.getIteratorSize _).sum
其实SparkContext实现了很多runJob,这些函数的区别就是调用参数不同,但是这些runJob最后都会
调用DAGScheduler的runJob:
dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, allowLocal, resut)
在DAGScheduler的runJob会开始对用户提交的Job进行处理,包括Stage的划分、Task的生成等。
TaskScheduler是通过SparkContext#createTaskScheduler创建,而DAGScheduler时直接调用它的构造函数创建。只不过DAGScheduler保存了TaskScheduler的引用,因此需要在TaskScheduler创建之后进行创建。
在sparkContext中创建DAGScheduler;
dagScheduler = new DAGScheduler(this);
这个构造函数的实现是:
def this(sc: SparkContext) = this(sc, sc.taskScheduler)
继续看this(sc, sctaskScheduler)的实现:
def this(sc: SparkContext, taskScheduler:TaskScheduler) = {
this(
sc,
taskScheduler,
sc.listenerBus,
sc.env.mapOutTracker.asInstanceOf[MapOutputTrackerMaster],
sc.env.blockManager.master
sc.env
)
}
this(sc, sc,taskScheduler)通过调用下面的构造函数完成DAGScheduler的创建:
private[spark]
class DAGScheduler(
private[scheduler] val sc : SparkContext,
private[scheduler] val taskScheduler: TaskScheduler,
listenerBus: LiverListenerBus,
mapOutputTracker: MapOutputTrackerMaster, //shuffle map task 的输出,下游的task可以获取shuffle的位置信息
blockManagerMaster: BlockManagerMaster, //在Driver端管理整个Job的Block的信息。
env: SparkEnv,
clock: Clock = SystemClock
)extends Logging{}
这里我们看见DAGScheduler初始化的都是集群状态信息的数据结构,对于监管它还会创建一个Actor(org.apache.spark.scheduler.DAGSchedulerEventProcessActor),变量名为event-ProcessActor
。这个Actor的主要职责就是处理DAGScheduler发送给它的各种消息;
def receive = {
case p: Props => sender ! context.actorOf(p)
case _=> logWarning("received unknown message in DAGSchedulerActorSupervis")
}
//DAGSchedulerEventProcessActor的创建过程
implicit val timeout = Timeout(30 seconds)
val initEventActorReply = dagSchedulerActorSupervisor ? Props(new DAGSchedulerEventProcessActor(this))
eventProcesActor = Await.result(initEventActorReply, timeout.duration).asInstanceOf[ActorRef]
而DAGSchedulerActorSupervisor来完成eventProcessActor的创建。如果Actor出现错误,则取消DAGScheduler的所有job,停止SparkContext,最终退出。
2、count具体的执行过程
言归正传前面是介绍了DAGScheduler的内部创建机制,以及我们在调用count的时候sparkContext是如何调用,已经如何创建DAGScheduler和TaskScheduler的。
那么抛开方法里面的细节,整个count的执行流程如下
由上面图可大致了解count执行的具体过程,第三到第四步的实现:
val waiter = submitJob(rdd, func, partitions, callSite, allowLocal, resultHandler)
waiter.awaitResult() match{
case JobSucceeded => {
logInfo("Job %d finished: %s, took %f s".format(waiter.jobId,callSite.shotForm,(System.nanoTime - strat)/1e9))
case JobFailed(exception: Exception) =>
logInfo("Job %d failed: %s, took %f s").format(waiter.jobId, callSite.shortForm, (System.nanoTIme - start)/1e9)
}
}
上述submitJob首先会为这个Job生成一个Job ID,并且生成一个JobWaiter的实里来监听Job的执行情况:
val waiter = new JobWaiter(this, jobId, partitions.size, resultHandler)
JobWaiter会监听Job的执行状态,而Job是由多个Task组成的,因此只有Job的所有Task都成功完成,Job才标记成功。当其中一个Task失败,都会标记Job失败,这是DAGScheduler通过调用org.apache.spark.scheduler.JobWaiter#jobFailed实现的。
最后,DAGScheduler会向eventProcessActor提交该Job:
eventProcessActor ! JobSubmitted()