写在前面的话:本篇博客为原创,认真阅读需要比对spark 2.1.1的源码,预计阅读耗时30分钟,如果大家发现有问题或者是不懂的,欢迎讨论
欢迎关注公众号:后来X
spark 2.1.1的源码包(有需要自取):关注公众号【后来X】,回复spark源码
第一次写博客,写的有啥问题,欢迎大家留言评论,一定每周更新,哈哈!
今天主要分析的是spark的YarnCluster模式下的提交任务的源码,那么我们先看一下流程图
开始啃源码吧,为了啃源码更高效,希望大家把这张流程图搭配着一起看,可以时刻知道现在到哪一步了。
正式开始源码分析
Spark-submit命令
说到提交任务,不管是什么spark的哪种运行模式,提交任务的命令都少不了Spark-submit,下面以提交wordCount的项目的命令为例:
bin/spark-submit \
--class com.later.WordCount \
--master yarn \
--deploy-mode cluster \
./test/jars/spark-WordCount.jar \
10
那我们提交完之后,都会有一个spark-submit的线程,所以在spark源码中,double shift我们先找到SparkSubmit.scala
-
先找到main方法,并且提交参数的 .action 默认为submit,所以匹配到提交作业的命令:submit(appArgs)
-
准备提交job的环境:prepareSubmitEnvironment(args)
在这个里面,主要是对参数的赋值,我们额外注意childMainClass,通过ctrl+F搜索,发现:
cluster模式->childMainClass = “org.apache.spark.deploy.yarn.Client”
client模式->childMainClass = “com.later.WordCount” -
最后doRunMain(),点进去
-
判断代理是否为null,无论是if还是else,都执行
runMain(childArgs, childClasspath, sysProps, childMainClass, args.verbose)
-
在这里发现:定义了一个mainClass对象,并把上面第2步拿到的childMainClass赋给了mainClass
-
并且往下滑,发现还通过反射的方式获得mainClass中的main方法,赋给了mainMethod
-
将mainClass中的main方法当做自己的方法调用(回调)
此时的mainClass对象为:org.apache.spark.deploy.yarn.Client
yarnClient的启动
因为在上一次submit最终回调的是这个Client里面的main方法,所以double shift找到org.apache.spark.deploy.yarn.Client
-
找到main方法,并且创建了Client,执行了run方法
-
向ResourceManager提交申请,获取appID,点进去
-
既然提出申请,那就需要
**创建申请: ** val newApp = yarnClient.createApplication()
创建该申请的响应: val newAppResponse = newApp.getNewApplicationResponse()
设置启动AM的环境:
val appContext = createApplicationSubmissionContext(newApp, containerContext)
-
这是设置适当的上下文来启动AM,也就是封装命令的具体内容 :
val containerContext = createContainerLaunchContext(newAppResponse)
那么既然要启动AM,就需要有相应的参数,在该方法中点进去,可以看到:javaOpts+=内存,GC,日志
并且获取到了val userClass = 我们在提交任务的时候,–class 之后提交的参数,例如“com.later.wordCount”,并且还拿到了amClass
cluster->val amClass = “org.apache.spark.deploy.yarn.ApplicationMaster”
client->amClass=“org.apache.spark.deploy.yarn.ExecutorLauncher”
AM的命令:val commands =
/bin/java “org.apache.spark.deploy.yarn.ApplicationMaster” --class WordCount… -
接着第3步往下,看到了提交命令:yarnClient.submitApplication(appContext)
到此,Client已经向RM提交了申请,由RM指定一个NM来执行封装的命令,启动AM
ApplicationMaster的启动
所以我们接下来的代码应该从上面的command中:org.apache.spark.deploy.yarn.ApplicationMaster
-
先找到main方法,那既然是来启动AM的,所以就先创建一个AM,并且执行了master.run()
-
接下来看run方法,大多数都是变量的赋值,其中包括创建HDFS文件系统:val fs = FileSystem.get(yarnConf)
-
并且找到Driver的执行:runDriver(securityMgr)
-
在Driver方法中,启动了用户类线程:userClassThread = startUserApplication()
-
用户类线程中:用类加载器的方式来加载用户类的main方法,并且,为这个线程设置名称为"Driver",验证了在yarn的cluster模式下,Driver运行在集群(在client模式下,Driver运行在客户端)
-
如图第4步中,除了用户类线程,还有向RM注册AM
-
现在有了AM,得需要向RM申请资源:allocator.allocateResources(),这里的方法名是分配资源:为RM为AM分配资源,也就是我说的申请资源
-
既然申请资源,肯定得获取资源容器, 判断申请到的资源容器大小是不是大于0,也就是说如果RM没资源了,返回的肯定是个空的容器,如果>0,就进行处理这些资源:
-
对获取到的资源进行分类(同一机架还是什么情况)
-
启动runAllocatedContainers(containersToUse),就是在这个Container里面运行ExecutorBackend
-
遍历可用的资源容器,对每一个进行如下操作:
for (container <- containersToUse) {
launcherPool.execute{new ExecutorRunnable().run()},找到了run方法
-
真正启动Container
-
那startContainer()这个启动方法进去,看一下是怎么启动的
ctx对命令进行了设置,ctx.setCommands(commands.asJava)
并且让NMClient开始运行 容器:nmClient.startContainer(container.get, ctx)
其中的准备命令为:val commands = prepareCommand(),点进去发现和之前的Client中的第4步有点类似。
命令是这样的:
/bin/java org.apache.spark.executor.CoarseGrainedExecutorBackend
以上是Yarn Cluster模式下,从Spark-submit提交任务开始,到Excutor执行的全过程
那我们再次返回来看这张流程图,是不是觉得前面的部分已经比较熟悉了。
下一篇我们能继续看ExecutorBackend以及任务的划分,下一篇再见。
最后再来一波,喜欢我的欢迎点赞,欢迎关注我的公众号:后来X,回复:spark源码,获取spark2.1.1源码包
持续更新,未完待续!