由于工作中生产环境大多使用的是Yarn-cluster模式,所以我将以Yarn-cluster模式作为主线对流程进行讲解。
目录
3.ApplicationMaster(ExecutorLauncher)
现在我们提交一个spark任务
spark-submit \ --master yarn-cluster \ --driver-cores 2 \ --driver-memory 1g \ --executor-cores 4 \ --num-executors 10 \ --executor-memory 8g \ --class PackageName.ClassName XXXX.jar \ --name "Spark Job Name" \ --queue spark \ InputPath \ OutputPath |
此时相当于执行了 java SparkSubmit -xxx 。。。。。。
相当于开启了一个JVM进程,执行了SparkSubmit类中的main方法。找到SparkSumit.scala类
1.submit
def main(args: Array[String]): Unit = {
val appArgs = new SparkSubmitArguments(args)
if (appArgs.verbose) {
// scalastyle:off println
printStream.println(appArgs)
// scalastyle:on println
}
appArgs.action match {
case SparkSubmitAction.SUBMIT => submit(appArgs)
case SparkSubmitAction.KILL => kill(appArgs)
case SparkSubmitAction.REQUEST_STATUS => requestStatus(appArgs)
}
}
首先val appArgs = new SparkSubmitArguments(args) 封装参数,同时判断参数合法性等。
再进行模式匹配,提交时会走SparkSubmitAction.SUBMIT => submit(appArgs)
注意:在SparkSubmitArguments.scala类中有这样一段代码
// Action should be SUBMIT unless otherwise specified
action = Option(action).getOrElse(SUBMIT)
}
因此此处的模式匹配默认会进行提交,所以不用添加case_ 的条件。
现在继续:
进入 submit(appArgs)方法,首先开始准备提交环境
val (childArgs, childClasspath, sysProps, childMainClass) = prepareSubmitEnvironment(args)
注意此处的类型推断,直接将返回值赋给了childArgs, childClasspath, sysProps, childMainClass这几个变量(这部分一会说,标记为①,先往下走)
下面会执行doRunmain()方法,其中也包括runMain方法,主要区别在于是否使用代理用户,此处可以理解为方法统一:
runMain(childArgs, childClasspath, sysProps, childMainClass, args.verbose)
进入runMain方法
此方法中:通过反射,加载了类(childMainClass类,是参数中的其中一个)
mainClass = Utils.classForName(childMainClass)
再找到这个类中的main方法
val mainMethod = mainClass.getMethod("main", new Array[String](0).getClass)
最后再通过invoke,调用该类的main方法
mainMethod.invoke(null, childArgs.toArray)
注意:由于此时是通过反射机制条用的main方法,所以不产生JVM进程,而是在SparkSubmit进程中创建了一个此方法的线程。
那现在执行的childMainClass这个类是谁呢?
现在回过头看标记①
val (childArgs, childClasspath, sysProps, childMainClass) = prepareSubmitEnvironment(args)
进入prepareSubmitEnvironment方法中
这个方法非常的长(通过返回值childArgs, childClasspath, sysProps, childMainClass可以找到这个方法的结尾),从判断条件中可以找到:
首先
if (deployMode == CLIENT || isYarnCluster) {
childMainClass = args.mainClass
然后:
if (isYarnCluster) {
childMainClass = "org.apache.spark.deploy.yarn.Client"
所以 在cluster的时候,childMainClass =》org.apache.spark.deploy.yarn.Client
在client的时候,childMainClass =》 参数中提交的类args.mainClass
到此,clister现在的部署的图形为:
2.client
进入org.apache.spark.deploy.yarn.Client类。
如果找不到需要在pom中引入依赖,再刷新
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-yarn_2.11</artifactId>
<version>2.1.1</version>
</dependency>
首先找到main方法
先封装参数:
val args = new ClientArguments(argStrings)
然后:
new Client(args, sparkConf).run()
此处先通过构造方法new了一个Client对象,对象属性中包括:
private val yarnClient = YarnClient.createYarnClient
从而得到了访问Yarn的客户端对象,通过这个对象向Yarn提交任务。这个对象再执行run()方法
进入run()方法以后
this.appId = submitApplication()
通过submitApplication方法,返回额一个id,用于所有后续应用调度,任务监控页面展示等等,非常重要。
现在进入submitApplication,顾名思义,这个是提交任务的方法。
首先进行后台连接,用于通信。
launcherBackend.connect()
结尾通过yarnClient的提交方法进行提交
yarnClient.submitApplication(appContext)
其中:参数appContenxt决定了提交内容,具体提交了什么呢?在最终提交的上面有这样一段代码,说明了提交的内容:
val containerContext = createContainerLaunchContext(newAppResponse)
val appContext = createApplicationSubmissionContext(newApp, containerContext)
进入createContainerLaunchContext方法
方法中定义了很多JVM相关参数,如堆大小,栈大小,CMS垃圾回收器参数等,其中:
// Add Xmx for AM memory
javaOpts += "-Xmx" + amMemory + "m"
此处说名,正在为ApplicationMaster设置进程参数。
val amClass =
if (isClusterMode) {
Utils.classForName("org.apache.spark.deploy.yarn.ApplicationMaster").getName
} else {
Utils.classForName("org.apache.spark.deploy.yarn.ExecutorLauncher").getName
}
显然:yarn-cluster模式是org.apache.spark.deploy.yarn.ApplicationMaster,yarn-client模式是org.apache.spark.deploy.yarn.ExecutorLauncher,执行类中主方法,开启了一个JVM进程。
此处对不同提交模式设置不同的ApplicationMater执行类(此处也间接说明,不管yarn-cluster还是yarn-client模式,在创建ApplicationMaster时都会创建Client对象)
进入ApplicationMaster类,在最后
object ExecutorLauncher {
def main(args: Array[String]): Unit = {
ApplicationMaster.main(args)
}
}
显然说明了,yarn-client的ExecutorLauncher最后调的也是ApplicationMater,可以认为是等价的了。
此时,调度流程图为:
3.ApplicationMaster(ExecutorLauncher)
进入ApplicationMaster中,找到main方法
第一步同样封装参数:
val amArgs = new ApplicationMasterArguments(args)
创建应用管理器对象,并执行run方法
master = new ApplicationMaster(amArgs, new YarnRMClient)
System.exit(master.run())
进入run方法
if (isClusterMode) {
runDriver(securityMgr)
} else {
runExecutorLauncher(securityMgr)
}
进入cluster模式的runDriver方法
首先启动用户应用
userClassThread = startUserApplication()
进入startUserApplication方法:获取用户应用类的main方法
val mainMethod = userClassLoader.loadClass(args.userClass)
.getMethod("main", classOf[Array[String]])
创建了一个用户线程,把这个线程起名为 Driver,并调用start方法启动
val userThread = new Thread {
override def run() {
。。。
}
userThread.setContextClassLoader(userClassLoader)
userThread.setName("Driver")
userThread.start()
userThread
}
Driver线程进行执行,现在回到runDriver方法,继续往下走,开始向Yarn注册ApplicationMaster,建立两者的关系;两个进程之间使用rpc(参数中的rpcEnv)进行交互
registerAM(sc.getConf, rpcEnv, driverRef, sc.ui.map(_.appUIAddress).getOrElse(""),securityMgr)
进入registerAM方法
代码最后部分:获取Yarn资源
allocator = client.register(driverUrl,
driverRef,
yarnConf,
_sparkConf,
uiAddress,
historyAddress,
securityMgr,
localResources)
追溯其中的client,是ApplicationMaster中的yarnRMClient,所以此处注册就是Driver向ResourceManager进行注册,目的是申请资源。
获取到之后,开始分配资源:
allocator.allocateResources()
进入allocateResources方法:拿到可分配的资源:
val allocateResponse = amClient.allocate(progressIndicator)
val allocatedContainers = allocateResponse.getAllocatedContainers()
拿到之后,处理资源:
handleAllocatedContainers(allocatedContainers.asScala)
再进入handleAllocatedContainers方法,一系列本地化(涉及数据移动优化等,不详细说了)后,得到需要运行的container
然后开始运行可用的资源容器:
runAllocatedContainers(containersToUse)
进入runAllocatedContainers方法:
launcherPool.execute(new Runnable {
override def run(): Unit = {
try {
new ExecutorRunnable(
Some(container),
conf,
sparkConf,
driverUrl,
executorId,
executorHostname,
executorMemory,
executorCores,
appAttemptId.getApplicationId.toString,
securityMgr,
localResources
).run()
创建线程池,ExecutorRunnable(...).run()
点进new ExecutorRunnable中:
var rpc: YarnRPC = YarnRPC.create(conf)
var nmClient: NMClient = _
def run(): Unit = {
logDebug("Starting Executor Container")
nmClient = NMClient.createNMClient()
nmClient.init(conf)
nmClient.start()
startContainer()
}
显然此处是在得到资源后,与其他NameNode进行交互,启动container
点击进入startContainer方法
val commands = prepareCommand()
进入prepareCommand方法
此方法中又出现了很多jvm参数,去执行一个进程
val commands = prefixEnv ++ Seq(
YarnSparkHadoopUtil.expandEnvironment(Environment.JAVA_HOME) + "/bin/java",
"-server") ++
javaOpts ++
Seq("org.apache.spark.executor.CoarseGrainedExecutorBackend",
"--driver-url", masterAddress,
"--executor-id", executorId,
"--hostname", hostname,
"--cores", executorCores.toString,
"--app-id", appId) ++
userClassPath ++
此时运行的进程就是
org.apache.spark.executor.CoarseGrainedExecutorBackend
进入到CoarseGrainedExecutorBackend类中,可以找到main方法
main方法中:
env.rpcEnv.setupEndpoint("Executor", new CoarseGrainedExecutorBackend(
env.rpcEnv, driverUrl, executorId, hostname, cores, userClassPath, env))
定义了一个“Executor”用于进程间的交互,注意这个不是我们平时所说的Executor
进入new CoarseGrainedExecutorBackend,我们会发现:
var executor: Executor = null
这才是我们用来计算的Executor对象,是类中的一个属性。
其他节点后续交互部分就略过,会在下面的图中的流程中提示。到此yarn-cluster部署的流程到此结束,此时的流程图为:
最后附上比较权威的yarn-client和yarn-cluster的提交流程图
yarn-cluster模式
yarn-client模式: