前言
最近阅读一下 Spark 的部分源码,在这一过程中通过源码结合之前所了解的相关内容,能够对之前知识进行完整的梳理也能更一进步了解 Spark 运行的底层逻辑,由于阅读源码是一个较为艰深的过程遂将其记录下来方便日后回顾,本篇我们来讲一下我们的 Spark on Yarn 在提交一个任务后俩个框架为我们做了些什么。
Spark 向 Yarn 提交任务的流程
在 Linux 上安装完 Spark 后都会用一个官方提供的 example 来测一测我们的 Spark 安装成功了,比如下面这段命令:
bin/spark-submit \
--master yarn \
./examples/jars/spark-examples_2.11-2.1.1.jar \
--class org.apache.spark.examples.SparkPi \
100
上面命令的具体参数含义就不说了,从我们执行的 shell 名 spark-submit
就可以看出我们是在做一个提交行为,而我们又是向 Yarn 提交的任务因此我们就会得到如下的这张流程图,而本文的目的就是通过源码来看看我们的 Spark 到底是如何一步一步执行图中的步骤的,为了有助于理解我们还是先用文字略微详细的描述一下下图。
- Spark Client 将我们需要执行的应用提交给 Yarn 集群中的 RM (resource manager)
- RM 收到后在一台 NM (node manager) 中启动 AM (application master)
- AM 在计算所需要的资源后向 RM 申请资源
- RM 返回可用的资源列表给 AM
- AM 根据资源列表在相应的 NM 上启动 Container 并在其中启动 Executor
- Executor 向 AM 注册自己
- AM 将任务分解并分发给各个 executor
一般来说至此我们就大致了解了整个流程,如果你对 Yarn 很了解那么整个流程就更加明了了,因为在 Yarn 上提交任务基本都是差不多的流程,但就如开头所说我们要从源码的角度来看看究竟为啥是这些流程,因此我们可以先记住这种图再读完接下来的内容后再回过头来看看是否相符。
Spark 提交任务源码
Spark Submit
我们本次使用的工具是 IDEA 其方便的搜索功能能够帮助我们顺畅的进行阅读,首先让我们回到开头的那个命令
bin/spark-submit \
--master yarn \
./examples/jars/spark-examples_2.11-2.1.1.jar \
--class org.apache.spark.examples.SparkPi \
100
我们执行的 shell 叫 spark-submit
当我们执行后其启动了一个进程,安装我们对于 Java 相关的知识其一定是执行了一个 main()
函数,于是我们打开这个 shell 看看其执行的是那个类,我们打开后就会发现有如下的这行代码
exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"
可以看到我们就找到了执行的主类org.apache.spark.deploy.SparkSubmit
,就是通过它来完成我们的应用提交的,接下来我们来看看这个类中的 main()
函数做了那些事情,代码如下:
def main(args: Array[String]): Unit = {
// 封装 Spark 提交参数
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)
}
}
比较重要的两行代码我已经注释出来了,可以看到主要是做了两件事情,1)将我们提交的参数封装成一个对象;2)将参数封装成对象后执行提交,我们先来看第一步,代码如下:
private[deploy] class SparkSubmitArguments(args: Seq[String], env: Map[String, String] = sys.env)
extends SparkSubmitArgumentsParser {
var master: String = null
var deployMode: String = null
var executorMemory: String = null
var executorCores: String = null
var totalExecutorCores: String = null
.....
var mainClass: String = null
var primaryResource: String = null
var name: String = null
var childArgs: ArrayBuffer[String] = new ArrayBuffer[String]()
var jars: String = null
......
进到 SparkSubmitArguments
这个类立马就会看到上述代码内容 (有省略),我们很快就能发现有一些变量是我们在命令行中输入的比如 master -> --master, mainClass-> --Class
可见我们在 spark-submit
后添加的参数都被封装进了这个对象中,知道了我们的参数是如何封装的之后我们就可以进入到提交了我们进入 submit
来看看其做了那些事情代码如下 (方便阅读有部分省略):
private def submit(args: SparkSubmitArguments): Unit = {
// 准备提交环境
val (childArgs, childClasspath, sysProps, childMainClass) = prepareSubmitEnvironment(args)
def doRunMain(): Unit = {
if (args.proxyUser != null) {
...
try {
proxyUser.doAs(new PrivilegedExceptionAction[Unit]() {
override def run(): Unit = {
// 根据提交环境运行 main 函数
runMain(childArgs, childClasspath, sysProps, childMainClass, args.verbose)
}
})
} catch {