spark的调度包括资源调度和任务调度两种。前面介绍过spark的任务调度包括DAGScheduler&TaskScheduler,本文主要介绍资源调度,包括资源调度中涉及的各模块以及它们之间的通信。
Spark中物理节点包括三者,分别是client、master&worker,而与资源调度相关的角色有四个,分别是driver、master、worker和executor。这几个节点与角色之间的关系可以从下面这张图中描述:
Driver:用于运行Spark应用程序,其中的SparkContext是spark应用程序的入口。根据spark任务提交方式的不同,Driver可以运行在不同的物理节点上,通过spark shell提交时,driver运行于master节点,通过spark-submit提交时,driver运行于client端。
worker:集群中任何可以运行Spark应用程序的物理节点,对应物理机,运行一个或多个Executor进程;
Executor:内部是一个线程池,交给Executor的Task都运行在其中的一个线程,线程间共享资源。此外,Executor有一个代理类ExecutorBackend用于与TaskScheduler&TaskSetManager等模块保持通信,这部分涉及任务调度,这里不再详述。
这几个角色之间的启动流程如下:
SparkContext.createTaskScheduler->taskScheduler.start()->SchedulerBackend.start()->new AppClient&start
还有一条线:
AppClient.onStart->向master注册Application(RegisterApplication)->master接收RegisterApplication,调用scheduler,启动driver&executor->调用startExecutorsOnWorks->launchExecutor
这里注意两点:
a):SchedulerBackend的start方法中先调用super.start开启了CoarseGrainedSchedulerBackend。
在CoarseGrainedSchedulerBackend的start方法中,做了一个重要的事,就是创建dirverEndpoint:
b):还有一个重要的ClientEndPoint,创建于AppClient的初始化过程中。
几个角色接收处理的消息列出如下(均位于各自的receive函数中):
Worker:SendHearbeat、WorkDirCleanup、MasterChanged、ReconnectWorker、LaunchExecutor、ExcutorStateChanged、KillExecutor、LaunchDriver、KillDriver、DriverStateChanged、RegisterWithMaster、ApplicationFinished
CoarseGrainedExecutorBackend:RegisteredExecutor、RegisterExecutorFailed、LaunchTask、KillTask、StopExecutor、Shutdown
AppClient:RegisteredApplication、ApplicationRemoved、ExecutorAdded、ExecutorUpdated、MasterChanged
LocalBackend:ReviveOffers(executor.launchTask)、StatusUpdate、KillTask
CoarseGrainedSchedulerBackend:StatusUpdate、ReviveOffers、KillTask(receive)
CoarseGrainedSchedulerBackend:RegisterExecutor、StopDriver、StopExecutors、RemoveExecutor、RetrieveSparkProps(receive and replay)
(receive无需等待答复,而receiveAndReply则会阻塞线程,直至有答复)
这几个角色之间的主要交互流程如下:
1、driver-> master:提交Application,向master注册并申请资源;
AppClient.onStart()中通过调用masterRef.send(RegisterApplication(appDescription, self))向master发送消息
master接收消息后,依次调用createApplication、registerApplication、startExecutorsOnWorkers();
注意的是startExecutorsOnWorkers中步步调用,完成了为Application分配Executors,并分配计算资源。
2、master->driver:共传递两种消息
1、告知Application提交成功,回复RegisteredApplication给driver
2、告知driver Executor分配成功,在startExecutorsOnWorkers的最后发送
3、executor->driver:注册executor,注册成功的消息会发送给CoarseGrainedExecutorBackend用以创建具体executor
在
CoarseGrainedExecutorBackend的onStart()方法里向driver发送RegisterExecutor消息,这里的ref指代的就是上文的dirverEndpoint。driver会将executor信息存储在executorDataMap对象里,触发
CoarseGranedSchedulerBackend的makeOffers方法,分配pending的tasks(这里makeOffers开始进入任务调度流程,所以说makeOffers方法是资源调度和任务调度相融合的地方)。
这里详细说一下makeOffers方法,该方法是个private方法,会在两处调用:
1、case ReviveOffers :TaskScheduler submitTasks时会调用;
2、RegisterExecutor:上文一旦有executor注册,就看看有没有需要分配的任务;
进入makeOffers方法,它会首先调用scheduler.resourceOffers,resourceOffers负责taskSet进行task的调度分配,分配时尽量按就近原则进行分配,关键代码如下:
4、driver->executor:提交Tasks,由后者执行任务
makeOffers的最后调用launchTasks发布任务,此时是通过
CoarseGranedSchedulerBackend向CoarseGrainedExecutorBackend发送消息,CoarseGrainedExecutorBackend接收到消息后,开始任务的执行操作。
3、dirver->workers:发送appDescription,启动executor子进程
在前文所述的第二条线中,master的scheduler函数完成executor在worker节点上的分配,然后调用launchExecutor向woker.endpoint发出消息以启动executor。
最后感谢flykobe,本文的部分资料来自他的博客,spark采用akka通信,减少了并发带来的死锁问题,提高了并发效率,但阅读起源代码来难度增加不少。希望能和大家一起学习,共同提高。
参考资料: