spark运行流程分为资源环境准备和任务提交运行两个步骤,两个步骤交叉进行,当前以资源准备为主线进行源码分析。
另一条线可以参考:spark源码解析之三、任务切分与运行
关于源代码的前期准备可以参考:spark源码解析之一、整体概述
一、源码时序图
本次源码跟踪是在yarn-cluster模式下的原码,在源码中只关注cluster模式,如果没有特殊说明,默认yarn-cluster模式。
鉴于spark源码的复杂性,为了今后复习方便,按照时间先后顺序将spark应用启动的资源准备线绘制出时序图。在时序图中只是列举了重要的节点,与主题无关的类或者对象没有列举,在绘制图形是没有严格按照时序图的标准进行,有问题请各位即使批评指正,相互学习。
二、源码详细分析
源码上下文顺序与时序图顺序基本保持一致,一级标题例如标识类,这里没有展示全类名;二级标题标识该类下的方法,源码的上下文并没有按照标题顺序,而是按照逻辑调用的顺序进行,所以二级标题是错乱的,但是在一级标题下的二级标题是有序的。
1 SparkSubmit
spark_submit 提交任务,命令会调起脚本启动SparkSubmit进程,所以spark资源环境的准备的程序入口就从 SparkSubmit.main方法开始。
1.1 SparkSubmit.main
def main(args: Array[String]): Unit = {
//解析spark-submit命令提交的参数
val appArgs = new SparkSubmitArguments(args)
....
appArgs.action match {
//我们只关注提交SUBMIT的情况,参考1.2
case SparkSubmitAction.SUBMIT => submit(appArgs)
case SparkSubmitAction.KILL => kill(appArgs)
case SparkSubmitAction.REQUEST_STATUS => requestStatus(appArgs)
}
}
通过解析任务脚本提交的参数,这里我们只关注提交submit这一种情况,所以接下来关注SparkSubmit.submit方法。
1.2 SparkSubmit.submit
private def submit(args: SparkSubmitArguments): Unit = {
//解析提交环境参数,参考1.3
val (childArgs, childClasspath, sysProps, childMainClass) = prepareSubmitEnvironment(args)
//这里只是定义一个方法,不会自动执行,只有被调用才会执行
def doRunMain(): Unit = {
if (args.proxyUser != null) {
val proxyUser = UserGroupInformation.createProxyUser(args.proxyUser,
UserGroupInformation.getCurrentUser())
try {
proxyUser.doAs(new PrivilegedExceptionAction[Unit]() {
override def run(): Unit = {
//根据解析的环境资源参数调用子类方法
runMain(childArgs, childClasspath, sysProps, childMainClass, args.verbose)
}
})
} catch {
....
}
} else {
//参考1.4
runMain(childArgs, childClasspath, sysProps, childMainClass, args.verbose)
}
}
//StandaloneCluster模式
if (args.isStandaloneCluster && args.useRest) {
....
//其它模式
} else {
//调用doRunMain方法
doRunMain()
}
}
该方法主要分两步,预先准备任务提交的环境参数信息,然后将环境参数信息提交。首先关注SparkSubmit的prepareSubmitEnvironment方法,这里边的信息决定了我们接下来调用的方法。
1.3 SparkSubmit.prepareSubmitEnvironment
主要为了准备提交环境,这里主要关注childMainClass 。
private[deploy] def prepareSubmitEnvironment(args: SparkSubmitArguments)
: (Seq[String], Seq[String], Map[String, String], String) = {
....
//isYarnCluster:yarn集群模式
if (isYarnCluster) {
//需要特别关注
childMainClass = "org.apache.spark.deploy.yarn.Client"
....
}
....
(childArgs, childClasspath, sysProps, childMainClass)
}
在yarn集群模式模式下反回了一个重要的参数childMainClass = “org.apache.spark.deploy.yarn.Client”,接下来关注一下SparkSubmit.runMain。
1.4 SparkSubmit.runMain
下一步参考 2.1 main
private def runMain(
childArgs: Seq[String],
childClasspath: Seq[String],
sysProps: Map[String, String],
childMainClass: String,
verbose: Boolean): Unit = {
var mainClass: Class[_] = null
try {
//根据反射获取childMainClass的类信息。
mainClass = Utils.classForName(childMainClass)
} catch {
....
}
//获取childMainClass的main方法
val mainMethod = mainClass.getMethod("main", new Array[String](0).getClass)
if (!Modifier.isStatic(mainMethod.getModifiers)) {
throw new IllegalStateException("The main method in the given main class must be static")
}
try {
//反射调用org.apache.spark.deploy.yarn.Client的main方法
mainMethod.invoke(null, childArgs.toArray)
} catch {
....
}
}
该方法主要通过反射调用了org.apache.spark.deploy.yarn.Client的main方法。
2 Client
2.1 Client.main
def main(argStrings: Array[String]) {
....
//调用Client的run方法
new Client(args, sparkConf).run()
}
创建org.apache.spark.deploy.yarn.Client对象并调用run方法。
2.2 Client.run
//向ResourceManager提交应用程序
def run(): Unit = {
this.appId = submitApplication()
....
}
该方法主要是提交应用到ResourceManager。通过接下来主要关注一下Client.submitApplication。
2.3 Client.submitApplication
//向ResourceManager提交一个运行ApplicationMaster的应用程序
def submitApplication(): ApplicationId = {
var appId: ApplicationId = null
try {
launcherBackend.connect()
//在执行其他操作之前设置凭据
setupCredentials()
yarnClient.init(yarnConf)
//启动yarnClient,与yarn建立连接
yarnClient.start()
// 从AM创建一个应用
val newApp = yarnClient.createApplication()
val newAppResponse = newApp.getNewApplicationResponse()
appId = newAppResponse.getApplicationId()
....
new CallerContext("CLIENT", Option(appId.toString)).setCurrentContext()
....
//设置适当的上下文来启动AM
val containerContext = createContainerLaunchContext(newAppResponse)
val appContext = createApplicationSubmissionContext(newApp, containerContext)
....
//向yarn的ResourceManager提交应用
//这里主要提交前边方法封装的命令,提交之后才会执行createContainerLaunchContext封装的逻辑和命令
yarnClient.submitApplication(appContext)
appId
} catch {
....
}
}