spark源码学习(八):spark具体是如何使用集群的资源去运行任务
在前面的blog中谈到了sparkContext,DAGScheduler的初始化,TaskSeceduler的启动。Driver,Master,
Worker,Executor相关的注册和消息传递。还有resultStage,ActiveJob的创建以及stage的提交,划分,
ShuffleMapTask和ResultTasks的创建。前一部分是和spark集群的初始化和后台schedulerBackend调度模式的选
择,后一部分是和任务的运行相关的工作,但是,两者之间到底是如何去进行链接呢?也就是说,在集群启动之后,
我们去写一部分相关的RDD的操作,当遇到rdd的action操作要runJob的时候,是如何获取worker上的资源然后进行运
行任务的呢?下面我们来大体的介绍一下相关的代码。
在sparkContext的初始化分析中,在最后仅仅看到了这篇代码:
我们以standalone模式来看看具体的代码实现,进入CoarseGrainedSchedulerBackend的start方法:
最前的for代码块儿就是找出以在配置文件中以"spark"字符串开头的配置,因为在standalone下的配置都是以spark
抬头的。在添加完属性之后,就开始创建一个driverActor,这就是task和集群链接的核心之处。
在stage的划分和task的创建的时候,在最后一段这样的代码:
进入tashScheduleImpl的submitTasks的具体实现来看看:
看到了backend.reviveOffers函数,它主要是进行资源分配使用的,就是索取资源。因为你要想提交任务,总
得需要申请任务需求的资源吧。进入函数具体看看。这里我们已然进入 CoarseGrainedSchedulerBackend的revive-
offers函数:
既然发送了一个reviveOffers消息,那就去找找对应的接收消息,如下:
进入makeOffers函数去看看:
这里的首先把ececutorDataMap中的executorData转化成workerOffer。然后调用TaskSchedulerImpl的resource
Offers函数给当前的任务分配Executor,resourceOffers主要是用于task的资源分配,resourceOffer用于给Worker分
配task。那就进如LaunchTasks函数看看:
executorActor发送的LaunchTask消息,那就去看看接收到之后如何处理:
红线圈起来的代码应该在任务的提交和运行里面有看到。
其实,这里还有一个问题executor是任务执行的核心,但是executor是如何创建的呢?虽然我们在任务的提交
和运行那儿看到在worker接收到master的registeredExecutor消息之后会创建一个Executor.但是我们在分析这篇代码
的时候是直接从 CoarseGrainedExecutorBackend直接去分析的,那么问题就是:在stanalone的cluster中,什么时候
用到了这个 CoarseGrainedExecutorBackend的呢?下面就讨论这个CoarseGrainedExecutorBackend的创建,这个
class的确有点儿不容易发现。在stanalone源码分析的时候,我们在片尾分析到了LaunchExecutor函数:
workerActor在接收到LaunchExecutor消息之后会创建一个ExecutorRunner类。首先,创建executor的工作目录
创建App的本地目录,在app完成的时候会自动删除,创建并启动ExecutorRunner,向master发送ExecutorStateCha
nged消息。在启动executorRunner之后,会创建workerThread县城个shutdownHook县城,后者用于在worker关闭
时候杀死所有的Executor进程。而workerThread主要是调用fetchAndRunExecutor方法。如下所示:
在debug的模式下,会看到sun.java.command正是CommandUtils.buildProcessBuilder构建的command。具体是
sun.java.command=org.apache.spark.exexutor.CoarseGrainedExecutorBackend ......那么就当然进入这个类的main
方法去看看:
private def run() { val executorConf = new SparkConf val port = executorConf.getInt("spark.executor.port", 0) //第一步:给driver发送driverPropsFetcher消息获取spark属性 val fetcher = RpcEnv.create("driverPropsFetcher",hostname,port,executorConf,new SecurityManager(executorConf)) ...... // Create SparkEnv using properties we fetched from the driver. ...... //第二步:使用spark属性创建自身需要的ActorSystem这里本质上就相当于创建了ActorSystem //这里本质上就相当于创建了ActorSystem val env = SparkEnv.createExecutorEnv( driverConf, executorId, hostname, port, cores, isLocal = false) // SparkEnv sets spark.driver.port so it shouldn't be 0 anymore. val boundPort = env.conf.getInt("spark.executor.port", 0) assert(boundPort != 0) // 开启 CoarseGrainedExecutorBackend 的Actor. val sparkHostPort = hostname + ":" + boundPort //第三步:注册CoarseGrainedExecutorBackend到ActorSystem env.rpcEnv.setupEndpoint("Executor", new CoarseGrainedExecutorBackend(//看到了吧 env.rpcEnv, driverUrl, executorId, sparkHostPort, cores, userClassPath, env)) //第四步:注册WorkerWatcher到ActorSystem中 workerUrl.foreach { url => env.rpcEnv.setupEndpoint("WorkerWatcher", new WorkerWatcher(env.rpcEnv, url)) } env.rpcEnv.awaitTermination() SparkHadoopUtil.get.stopExecutorDelegationTokenRenewer() } }重点来了,在注册CoarseGrainedExecutorBackend会触发 onstart方法,去看看吧:
主要是向DriverActor发送RegisterExecutor消息,DriverActor接到RegisteredExecutor消息后处理的流程已经很
详细的介绍过了。至此,spark的集群的初始化和任务的提交的大致框架就说完啦。仅仅是大致的框架而已,具体的
函数分析以后再说吧。最后贴一张某大神画的用例图会更加帮助理解: