sparkYarn集群提交流程分析(一)
- spark提交流程图简介(之后的源码分析会围绕流程图进行)
源码分析
注意: 本文章中的所有代码都不完整 , 这里只取了重要代码分析
- 1 .首先集群提交就需要将用户编写的应用程序打成jar包上传到集群中
- 2 .其次在集群中执行的代码如下:
bin/spark-submit \
--class com.project.spark.WordCount \
--master yarn \
WordCount.jar \
/input \
/output
#注意: 这里的 \代表换行
- 3 .这里我们执行的是spark中bin目录下的spark-submit文件,文件中的代码主要是执行bin目录下的spark-class
exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"
#这里实际上执行的就是bin目录下的另一个文件spark-class
- 4 .在spark-class文件中总结出来的重要代码是
# Find the java binary
if [ -n "${JAVA_HOME}" ]; then
RUNNER="${JAVA_HOME}/bin/java"
else
if [ "$(command -v java)" ]; then
RUNNER="java"
else
echo "JAVA_HOME is not set" >&2
exit 1
fi
fi
build_command() {
"$RUNNER" -Xmx128m -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@"
printf "%d\0" $?
}
CMD=()
while IFS= read -d '' -r ARG; do
CMD+=("$ARG")
done < <(build_command "$@")
CMD=("${CMD[@]:0:$LAST}")
exec "${CMD[@]}"
1) . 获取java环境变量,然后拼接执行脚本/bin/java
2) . 加上参数封装执行第一个类org.apache.spark.launcher.Main将返回的数据封装到CMD中,实际上的作用就是对命令进行封装
3) . 最终大概可以得出的真实命令就是
${JAVA_HOME}/bin/java org.apache.spark.deploy.SparkSubmit "$@"
- 5 .实际上就是执行
SparkSubmit
类的main方法,至此就开始真正的源码分析 , 但是另外需要一些操作
查看源码需要在Maven项目中加入如下依赖,或者非Maven项目手动导入jar包
<!--读取yarn集群模式启动的程序源码-->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-yarn_2.11</artifactId>
<version>2.1.1</version>
</dependency>
- 5.1 bin/java执行的是
SparkSubmit
伴生类中的main方法 , 回顾java中的进程线程相关知识
- Java中main方法启动的是一个线程也是一个进程,一个java程序启动后它就是一个进程
- 这样的结论在scala中同样适用
1) 这样就可以得出提交命令首先在应用程序所在的集群开启了一个SparkSubmit进程,如下就是SparkSubmit的主函数
//这是org.apache.spark.deploy.SparkSubmit的main
def main(args: Array[String]): Unit = {
//将参数整合起来封装到Bean对象中一遍操作
val appArgs = new SparkSubmitArguments(args)
if (appArgs.verbose) {
printStream.println(appArgs)
}
//这里是主要代码,具体操作要根据appArgs.action来决定
appArgs.action match {
case SparkSubmitAction.SUBMIT => submit(appArgs)
case SparkSubmitAction.KILL => kill(appArgs)
case SparkSubmitAction.REQUEST_STATUS => requestStatus(appArgs)
}
}
- 5.1.1 由于模式匹配决定下一步的走向,模式匹配又依赖
appArgs.action
的值,这个值被封装在SparkSubmitArguments
对象中,进入到SparkSubmitArguments
类中找到appArgs.action
的值
//这个方法存在主构造器中会主动被调用
private def loadEnvironmentArguments(): Unit = {
...
//这里会对action这个属性进行初始化
action = Option(action).getOrElse(SUBMIT)
}
1) 在SparkSubmitArguments的主构造器中会主动地调用`loadEnvironmentArguments()`方法,方法中会对`action`进行赋值
- 5.1.2 获得了action的值是
SUBMIT
之后就会根据模式匹配进入submit()
方法中,主要代码如下:
private def submit(args: SparkSubmitArguments): Unit = {
val (childArgs, childClasspath, sysProps, childMainClass) = prepareSubmitEnvironment(args)
def doRunMain(): Unit = {
if (args.proxyUser != null) {
....
} else {
runMain(childArgs, childClasspath, sysProps, childMainClass, args.verbose)
}
}
if (args.isStandaloneCluster && args.useRest) {
...
} else {
doRunMain()
}
}
1) 首先看 if (args.isStandaloneCluster && args.useRest) 代码,因为在这里是Yarn集群提交的所以走 doRunMain() 方法
2) doRunMain方法中 args.proxyUser 一般没有,所以走 runMain() 方法
3) 最后我们要记住上面代码中 childMainClass 的具体值以后会使用的到,prepareSubmitEnvironment(args)中的相关代码
....
if (isYarnCluster) {
childMainClass = "org.apache.spark.deploy.yarn.Client"
....
4) 我们可以得到 childMainClass 的值为 org.apache.spark.deploy.yarn.Client
- 5.1.2.1 从
submit()
进入runMain()
后的主要代码如下
//根据字符串类型的类全限定名获取类对象
~ Class.forName(childMainClass)
//根据类对象获取当前类的main方法
~val mainMethod = mainClass.getMethod("main")
//调用指定类的主方法
~mainMethod.invoke(null, childArgs.toArray)
1) 从5.1.2 中的 4) 可以得到 childMainClass为 org.apache.spark.deploy.yarn.Client 由此可知此时程序将会执行 org.apache.spark.deploy.yarn.Client的main方法
- 6 这篇文章主要讲的是
应用程序
-->提交到集群本地创建SparkSubmit进程
-->执行org.apache.spark.deploy.yarn.Client的main方法