local与local-cluster部署
对于分布式集群,通常都会提供一种方便初学者入门学习、测试的部署模式,也就是在Hadoop框架中常说的本地集群模式,同样的,Spark框架也不例外,而且相对于其他框架,比如Hadoop,Spark框架提供了更丰富的本地集群模式。
在进一步解析之前,首先查看下SparkSubmit提交应用程序时,一些不支持的组合形式,对应代码如下所示:
1.private[deploy] def prepareSubmitEnvironment(args: SparkSubmitArguments) 2.…… 3.case (LOCAL, CLUSTER) => printErrorAndExit("Cluster deploy mode is not compatible with master \"local\"") 4.,,,,,, |
即,在使用local与local-cluster这种local方式时,不支持以CLUSTER的部署模式提交应用程序。
和上面化繁为简进行代码解析一样,在大致浏览整体代码,了解SparkContext执行的主要流程后,再以local与local-cluster部署为主线,分析本地集群部署模式下的处理细节。
可以从前面的分析中抽取出本地模式部署的TaskScheduler与SchedulerBackend的具体子类的实例构建信息,如表1-7所示:
表1-7三种Local部署时的TaskScheduler与SchedulerBackend的具体子类
部署模式(master) | 实例对应的类 | 备注 |
"local" | _taskScheduler:TaskSchedulerImpl _schedulerBackend:LocalBackend | 最简单的本地模式。 这种本地模式下,任务的失败重试次数为1,即失败不重试。 |
local[*]、local[N] | 指定线程个数的本地模式,指定方式及最终的线程数如下: 1)local[*]:当前处理器个数。 2)local[N]:指定的N。 这种本地模式下,任务的失败重试次数为1,即失败不重试。 | |
local[*, M]、 local[N, M] | 指定线程个数以及失败重试次数的本地模式,仅比上一种本地模式多了一个失败重试次数的设置,对应为M。 | |
local-cluster[numSlaves, coresPerSlave, memoryPerSlave] | _taskScheduler:TaskSchedulerImpl _schedulerBackend:SparkDeploySchedulerBackend | 本地伪分布式集群,由于本地模式下没有集群,因此需要构建一个用于模拟集群的实例:localCluster = new LocalSparkCluster。 对应的三个参数: numSlaves:模拟集群的Slave节点个数。 coresPerSlave:模拟集群的各个Slave节点上的内核数。 memoryPerSlave:模拟集群的各个Slave节点上的内存大小。 |
本地部署模式可以分为本地模式和本地伪分布式模式,其中,前三种对应为本地模式,而local-cluster这种则对应本地伪分布式模式。在三种本地模式中,内部实现的实例相同,仅仅是启动的线程数与任务失败重试的次数不同。尤其是在最后一种本地伪分布式模式中,通过提供与Spark Standalone部署集群对应的信息来模拟完全分布式的集群。
下面分别针对这几种本地部署模式进行分析。
在该模式下,使用一个工作线程执行计算任务,并且在任务失败时,不会重新计算,即在失败重启机制中使用重启次数为1。对应的控制变量参考代码如下:
1.// 当本地运行时,任务失败时不会重新执行。 2.// When running locally, don't try to re-execute tasks on failure. 3.val MAX_LOCAL_TASK_FAILURES = 1 |
该模式相当于在local部署的基础上增加了线程个数的控制。
1)local[*]模式,线程个数取决于本机的处理器个数,逻辑上保证一个处理器对应一个处理线程。
2)local[N]模式,则直接指定了使用的线程个数为N。
这两种模式下,也不会启动任务失败重试的机制,对应控制参数同local模式。
1.3.3 local[*, M]与local[N, M]部署
在该模式下,除了和上一种类似的线程个数控制之外,还增加了任务失败重试机制的失败重启次数的配置,即可以指定任务失败重试的最大次数为M。
在前三种本地模式中,SchedulerBackend的实现都是LocalBackend,该类的注释如下所示:
1./** 2.* 在以下情况时使用LocalBackend:运行一个Spark的本地模式,其中, 3.*executor, backend和master运行在同一个JVM进程中。 4.*LocalBackend is used when running a local version of Spark where the executor, backend, 5.* and master all run in the same JVM. It sits behind a TaskSchedulerImpl and handles 6.*launching tasks on a single Executor (created by the LocalBackend) running locally. 7.*/ 8.private[spark] class LocalBackend( 9.conf: SparkConf, 10.scheduler: TaskSchedulerImpl, 11.val totalCores: Int) |
在此简单给出内部实现流程的解析,具体步骤如下所示:
1)对应的初始化代码在前面提到的SparkContext类中的主要流程的createTaskScheduler方法中,构建TaskScheduler实例(这里具体子类为TaskSchedulerImpl)后,在该实例的初始化时传入同时构建的SchedulerBackend实例(这里具体子类为LocalBackend)。
2)构建出TaskScheduler实例后,会调用实例的start方法,在该方法中首先会调用SchedulerBackend的start方法。
3)在SchedulerBackend的start方法中,会构建出一个LocalEndpoint实例,在该实例中就会实例化出一个Executor,Executor实例负责具体的任务执行。
4)之后就是TaskScheduler进行作业调度,调用SchedulerBackend的reviveOffers()方法,然后由该方法向LocalEndpoint实例发送ReviveOffers消息。
5)最终在LocalEndpoint实例处理ReviveOffers消息时,启动Task,其他处理类似。对应Task的启动代码如下:
1.def reviveOffers() { 2.val offers = Seq(new WorkerOffer(localExecutorId, localExecutorHostname, freeCores)) 3.for (task <- scheduler.resourceOffers(offers).flatten) { 4.// 在Executor中会使用线程池的方式调度任务,而对应的作业调度是通过 5.// 判断当前可用Cores个数是否符合每个任务(Task)所需的Cores个数。 6.// 当符合该条件时更新当前可用Cores数freeCores,然后启动任务(Task) 7.freeCores -= scheduler.CPUS_PER_TASK 8.executor.launchTask(executorBackend, taskId = task.taskId, attemptNumber = task.attemptNumber, 9.task.name, task.serializedTask) 10.} 11.} |
其中Task的调度控制代码参考TaskSchedulerImpl的resourceOfferSingleTaskSet方法,其他调度的具体信息可以参考本书的调度章节。
上述三种local的部署模式,可以通过图1-2来加深理解:
图1-2 三种local的部署模式图
其中,TaskScheduler与SchedulerBackend的具体子类的具体子类分为为TaskSchedulerImpl与LocalBackend,具体的Task仍然在Executor中执行。
1.3.4 local-cluster[S, C, M]部署
格式如local-cluster[numSlaves, coresPerSlave,memoryPerSlave]的这种模式称为本地伪分布式部署模式,由于当前使用的是本地部署模式,因此不存在所谓的集群,所以在模拟伪分布式部署模式时,需要构建出一个模拟的集群模式。模拟的集群模式在代码中对应LocalSparkCluster实例。
在本地伪分布式部署模式中,构建的作业调度器同其他三种本地模式一样,也是实例化具体子类TaskSchedulerImpl,但同时构建的SchedulerBackend实例是和真实的Spark Standalone集群是一样的,也是实例化了SparkDeploySchedulerBackend子类。这说明本地伪分布式部署模式仅仅在集群组件构建的方式上有所差异,其他方面都是相同的。
对应的集群模拟,可以查看LocalSparkCluster的start方法,其中构建了Master和多个Worker实例来模拟分布式集群。模拟时使用的参数,也是参考Spark Standalone集群,通过numSlaves指定模拟集群中的Slaves节点个数,通过coresPerSlave指定模拟集群中各个Slave节点上的内核数,以及通过memoryPerSlave指定模拟集群中各个Slave配置的内存大小。
另外,本地模式在其他细节方面的影响,可以查看SparkContext中的本地模式控制变量的设置,对应变量定义的代码如下所示:
1.def isLocal: Boolean = (master == "local" || master.startsWith("local[")) |
通过查看isLocal所控制的地方,即可找到与本地模式相关的内容。