Executor是Spark执行任务的进程,Spark启动Executor过程包括如下步骤:
1)使用Spark-submit提交到集群,Master收到RequesSubmitDriver请求。
2)Master调用scheduler把Driver程序发送到worker端执行。
3)Driver执行时初始化SparkContext,创建AppClient,向Master注册,其中Appclient中实现了内部类ClientEndPoint,和Master进行通信。
4)Master收到注册信息后,完成application注册,调用Scheduler程序,向Worker发送LaunchExecutor请求,其中Scheduler主要有两个作用:完成Driver的调度,将waitingDriver数组中的Drivers发送的到满足运行条件的worker上执行(launchDriver函数);在Worker节点上启动Executor执行Application。
5)Worker启动ExecutorRunner,在ExecutorRunner中启动CoarseCrainedExecutorBackend,在其中创建Executor,完成向Driver注册。
Executor中CachedThreadPool是一个线程池分配线程,任务被分发到Executor中以TaskRunner线程形式申请线程池线程,执行。接下来介绍Executor的创建,分配和启动等关键操作。
Executor创建
上文已讲到Executor创建主要是首先由Scheduler线程启动,Scheduler线程通过调用startExecutorsOnWorkers方法完成,遍历worker选择出所有可用的workers,之后调用scheduleExecutorsOnWorkers进行worker选择,有两种策略:round-robin策略(默认),依次全占策略。分配好worker后调用allocateWorkerResourceToExecutor在worker上分配资源。主要调用launchExecutor,该函数向worker发送启动Executor请求,同时向driver返回新Executor启动信息。
下面转到worker端,worker收到launchExecutor消息后调用LaunchExecutor函数,主要负责创建本地目录,保存目录和appid的映射,创建ExecutorRunner线程,该线程负责下载依赖文件,并启动CoarseGaindExecutorBackend,此进程的启动方式是通过建造者模式,通过CommandUtils的ProcessBuilder创建于一个独立的JVM中,此线程向DriverActor发送registerExecutor信息,收到driverActor回复后创建Executor。
Executor通过CoarseGrainedExecutorBackend创建,所以运行于一个独立的JVM中,可以通过配置参数调整Executor占用资源大小,创建之前会进行参数的传递和配置。
Executor通信接口ExecutorBackend
ExecutorBackend是Executor向集群更新消息的接口,不同spark模式有不同实现。
Executor执行过程
DAGScheduler划分好Stage,通过submitMissingTasks分配好任务,将任务经过TaskScheduler的TaskSchedulerImpl的submitTask方法,将tasks加入调度pools,之后调用通信终端riviveOffers方法为Tasks指定Executor,最后想CoarseGranedExecutorBackend发送LaunchTasks信息。
Executor收到信息后会调用launchTasks方法,此方法会构建TaskRunner对象运行Tasks并放入线程池中执行。
TaskRunner作为一个线程类,run函数主要完成以下任务:
1)向driver发送stateUpdate信息
2)反序列出task和相关jar包
3)调用task的run方法,返回结果