Spark源码

环境准备(Yarn集群)

源码入手入口就是submit提交,当我们去提交的时候,整个程序就开始运行了。
在这里插入图片描述

当我们提交时

bin/spark-submit --class org.apache.spark.examples.SparkPi --master yarn ./examples/jars/spark-examples_2.12-3.0.0.jar 10

它底层实际调用的是bin目录中的spark-submit.cmd的脚本文件,让我们打开看看

在这里插入图片描述

spark-submit.cmd它内部调用的是spark-submit2.cmd脚本,打开spark-submit2.cmd脚本
在这里插入图片描述

在这里插入图片描述
同样它实际调用的是spark-class2.cmd脚本,打开spark-class2.cmd脚本

在这里插入图片描述
打开后我们发现了大量的脚本内容,在最后一行有个指令SPARK_CMD是我们真正要执行的指令,我们将他echo打印出来看一看

echo %SPARK_CMD%
终端执行一下submit提交程序

执行结果(java后面有个cp命令不重要这里过滤了)
在这里插入图片描述
这里是用java指令执行某一个类相当于打开一个java虚拟机(JVM),实际上等同于启动一个process进程,jps能查看的到

在这里插入图片描述

进入submit.doSubmit(args)方法

在这里插入图片描述
parseArguments(args)是解析参数方法,把传入的命令行解析,解析完成后应用参数的action动作方法去匹配某个分支(SUBMIT,KILL,REQUEST_STATUS,PRINT_VERSION),我们进入paserArguments(args)方法看看
在这里插入图片描述
这里他new了一个SparkSubmitArguments对象,进入看看
在这里插入图片描述
因为上一层new了他的实例对象,所以它的类会初始化,这个类里面有大量的属性

在这里插入图片描述

下面有一个方法在主体内容初始化的时候进行参数解析,会把我们传入的命令行参数转换解析,进入它看看

在这里插入图片描述
进入handle方法看看
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
name和value传进来和下列枚举类进行匹配,这里的MASTER就是我们传入参数的–master
在这里插入图片描述

我们进去看看
在这里插入图片描述
对应关系图:
在这里插入图片描述

只要设置对应的参数和参数值就可以通过以上方法进行匹配

返回到SparkSubmitArguments方法找到对action的处理
在这里插入图片描述
一开始将action赋值为null
在这里插入图片描述
这里对action进行操作
所以默认action就是SUBMIT,回到SparkSubmit类,匹配成功
在这里插入图片描述

这时候就可以提交了,进入submit提交方法
在这里插入图片描述
先声明了一个doRunMain()方法,先不看,往下走

在这里插入图片描述
我们用的是yarn集群,继续往下走到else,这里调用了一个最上面声明的doRunMain()方法,这时候我们在点到这个方法

在这里插入图片描述

进入runMain(args,uninitLog)方法
在这里插入图片描述
记着这个准备提交环境的方法,往下走
在这里插入图片描述
在这里插入图片描述
总结一下上面两个图:val app: SparkApplication = if (classOf[SparkApplication].isAssignableFrom(mainClass))这里会通过上面mainClass = Utils.classForName(childMainClass)反射获取的childMainClass类信息进行判断是否继承自SparkApplication这个类,如果是它会反射创建mainClass.getConstructor().newInstance().asInstanceOf[SparkApplication]这个类对象,如果不是它会new一个JavaMainApplication(mainClass)对象。所以我们关心的是这个childMainClass类型是什么,它从哪里来的?

在上面提到的准备提交环境方法val (childArgs, childClasspath, sparkConf, childMainClass) = prepareSubmitEnvironment(args),它返回的第四个值就是childMainClass,我们可以进去看看具体实现

在这里插入图片描述
进入到prepareSubmitEnvironment(args)方法,一开始childMainClass被赋值为空字符串,我们继续往下走
在这里插入图片描述
在这里插入图片描述
我们走的不是Standalone集群,只能进入Yarn集群,给了它childMainClass = YARN_CLUSTER_SUBMIT_CLASS这个值,我们进去查看一下这个值具体是什么

在这里插入图片描述

private[deploy] val YARN_CLUSTER_SUBMIT_CLASS =
        "org.apache.spark.deploy.yarn.YarnClusterApplication"

我们继续看org.apache.spark.deploy.yarn.YarnClusterApplication的源码,一般情况下如果未导入相应的spark-yarn源码包无法看到这里,相关依赖如下:

		<dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-yarn_2.12</artifactId>
            <version>3.0.0</version>
        </dependency>

在这里插入图片描述
在这里插入图片描述

我们点进去Client对象需要传入的对象ClientArgument(args)看看
在这里插入图片描述

回到上一级点进去Client类(文字描述跟不上的话直接双击shift搜上面类名进入)
在这里插入图片描述
这里有个yarnClient客户端,我们点进去createYarnClient
在这里插入图片描述
继续进入他的实现类
在这里插入图片描述

找到ResourceManager后返回到

new Client(new ClientArguments(args), conf, null).run()

进入他的run()方法

在这里插入图片描述

进入submitApplication()方法查看

在这里插入图片描述

在这里插入图片描述

createApplicationSubmissionContext方法内就是一些配置参数,也没有什么重要的部分,所以接着往上走进入createContainerLaunchContext方法(container在yarn中是起到一个解藕的作用),大概看一下里面有启动环境、本地资源、java虚拟机的一些配置

在这里插入图片描述

在这里插入图片描述

这里提交就是封装一些指令提交给ResourceManager,ResourceManager得到这个指令后,它会在某个节点(NodeManager)去运行这个指令,启动ApplicationMaster(启动的方式是/bin/java + 一些东西 + org.apache.spark.deploy.yarn.ApplicationMaster)

目前而言,从submit到启动ApplicationMaster的图是这样的:
在这里插入图片描述

查看org.apache.spark.deploy.yarn.ApplicationMaster源码,这个类能被/bin/java指令执行变成一个进程那他会有一个main方法,找到他的main()方法

在这里插入图片描述

有个ApplicationMasterArguments,它会对main方法传入的命令行参数进行封装,我们进入该方法查看
在这里插入图片描述

返回到上一层
在这里插入图片描述

往下看,找到ApplicationMaster方法进去查看

在这里插入图片描述

进入YarnRMClient类
在这里插入图片描述

有个AMRMClient(ApplicationMaster和ResourceManager之间连接的客户端),记住它就行,往后退到master实例对象

// 创建 ApplicationMaster 对象
        master = new ApplicationMaster(amArgs, sparkConf, yarnConf)

继续往下走
在这里插入图片描述

点进去执行AM对象的run()方法
在这里插入图片描述
往下走看到了一个判断是否集群模式的分支:如果是集群模式则执行runDriver()的方法,如果不是集群模式,也就是客户端模式,则启动ExecutorLauncher()方法,进入runDriver()方法查看

在这里插入图片描述
等待期间执行的是startUserApplication()方法,那么这个方法在干什么呢,我们进去这个方法查看

在这里插入图片描述

进去看看userClass是什么类
在这里插入图片描述

后退到
val mainMethod = userClassLoader.loadClass(args.userClass) .getMethod("main", classOf[Array[String]])
getMethod方法要从指定的类中找到main方法,但是并没有什么用途,接着往下走

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
这一部分总结就是Application根据我们传入的参数来启动driver线程,并初始化SparkContext
在这里插入图片描述
恰恰是因为这里初始化SparkContext后,等待结果返回成功,我们整个操作才可以继续往下走,所以我们每个项目部分(例如wordcount案例)
在这里插入图片描述
我们在配置好spark上下文初始化完成,我们整个案例才可以开始,继续

在这里插入图片描述

在这里插入图片描述
进入分配器创建方法查看
在这里插入图片描述

在这里插入图片描述
进入处理方法handleAllocatedContainers查看
这个方法把它粘贴下来

def handleAllocatedContainers(allocatedContainers: Seq[Container]): Unit = {
        val containersToUse = new ArrayBuffer[Container](allocatedContainers.size)
        
        // Match incoming requests by host
        // TODO 优先分配到同一台主机
        val remainingAfterHostMatches = new ArrayBuffer[Container]
        for (allocatedContainer <- allocatedContainers) {
            matchContainerToRequest(allocatedContainer, allocatedContainer.getNodeId.getHost,
                containersToUse, remainingAfterHostMatches)
        }
        
        // Match remaining by rack. Because YARN's RackResolver swallows thread interrupts
        // (see SPARK-27094), which can cause this code to miss interrupts from the AM, use
        // a separate thread to perform the operation.
        // TODO 分配到同一个机架
        val remainingAfterRackMatches = new ArrayBuffer[Container]
        if (remainingAfterHostMatches.nonEmpty) {
            var exception: Option[Throwable] = None
            val thread = new Thread("spark-rack-resolver") {
                override def run(): Unit = {
                    try {
                        for (allocatedContainer <- remainingAfterHostMatches) {
                            val rack = resolver.resolve(allocatedContainer.getNodeId.getHost)
                            matchContainerToRequest(allocatedContainer, rack, containersToUse,
                                remainingAfterRackMatches)
                        }
                    } catch {
                        case e: Throwable =>
                            exception = Some(e)
                    }
                }
            }
            thread.setDaemon(true)
            thread.start()
            
            try {
                thread.join()
            } catch {
                case e: InterruptedException =>
                    thread.interrupt()
                    throw e
            }
            
            if (exception.isDefined) {
                throw exception.get
            }
        }
        
        // Assign remaining that are neither node-local nor rack-local
        // TODO 不是同一主机,也不是同一机架
        val remainingAfterOffRackMatches = new ArrayBuffer[Container]
        for (allocatedContainer <- remainingAfterRackMatches) {
            matchContainerToRequest(allocatedContainer, ANY_HOST, containersToUse,
                remainingAfterOffRackMatches)
        }
        
        if (remainingAfterOffRackMatches.nonEmpty) {
            logDebug(s"Releasing ${remainingAfterOffRackMatches.size} unneeded containers that were " +
                s"allocated to us")
            for (container <- remainingAfterOffRackMatches) {
                internalReleaseContainer(container)
            }
        }
        // TODO 运行匹配后的资源
        runAllocatedContainers(containersToUse)
        
        logInfo("Received %d containers from YARN, launching executors on %d of them."
            .format(allocatedContainers.size, containersToUse.size))
    }
    

这个方法内会对节点进行分类(根据主机host和支架rack)
在这里插入图片描述
然后决定资源的分配方式和优先顺序(后面详细讲解)

在这里插入图片描述
准备工作完成就可以开始运行已分配容器,进入这个方法查看
每个可使用的容器挨个遍历

在这里插入图片描述

我们查看一下Executor的run方法

在这里插入图片描述

继续查看一下另外一个nm节点启动的container

组件通信

应用程序的执行

Shuffle

内存的管理

未完成,加班ing

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水花一直飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值