spark提交应用的全流程分析
来源:互联网 发布:周璇 五月的风 知乎 编辑:程序博客网 时间:2024/05/18 01:01
spark提交应用的全流程分析
@(博客文章)[spark]
本文分析一下spark的应用通过spark-submit后,如何提交到集群中并开始运行。
一、提交前准备
(一)脚本调用
1、spark-submit
spark通过spark-submit脚本来向集群提交应用,举个例子:
/home/hadoop/spark/bin/spark-submit --master yarn-client --num-executors 10 --class com.lujinhong.spark.ml.TrainModel myusml-0.0.1-SNAPSHOT.jar args1 args2 args3
我们看看spark-submit脚本,很简单,只有3行:
SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"# disable randomized hash for string in Python 3.3+export PYTHONHASHSEED=0exec "$SPARK_HOME"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"
其实就是调用spark-class这个脚本。
2、spark-class
spark-class完成了配置的加载:
. "$SPARK_HOME"/bin/load-spark-env.sh
以及调用上面说的SparkSubmit类
3、load-spark-env
有兴趣的可以看看如何加载配置,主要是spark-evn.sh文件,以及scala的版本等。
(二)SparkSubmit
1、main函数
很简单,appArgs解释命令行中的参数,然后判断action是什么,并执行相应的操作。
def main(args: Array[String]): Unit = { 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) } }
verbose是一个布尔值,用于确定是否打印一些JVM的信息,默认为false。
action的默认值是submit,我们这里也只分析submit的过程,因此下面将进入submit函数看目的地。
2、submit(args: SparkSubmitArguments): Unit
submit函数先是将参数转化为一个4元组的形式:
val (childArgs, childClasspath, sysProps, childMainClass) = prepareSubmitEnvironment(args)
然后就使用这些参数调用runMain函数了:
runMain(childArgs, childClasspath, sysProps, childMainClass, args.verbose)
其它内容分别考虑了使用proxy以及standalone的情形。下一步:runMain函数。
3、runMain函数
runMain函数开始执行Client类中的main函数了。
首先是一大堆的环境变量及参数的加载,判断类是否存在等,最后的目的是执行Client类中的main函数。
找到主类:
mainClass = Utils.classForName(childMainClass)
然后是主函数:
val mainMethod = mainClass.getMethod("main", new Array[String](0).getClass)
最后调用main方法:
mainMethod.invoke(null, childArgs.toArray)
那mainClass是哪个类呢?对于yarn-cluster来说,是:
if (isYarnCluster) childMainClass = "org.apache.spark.deploy.yarn.Client"
如果是yarn-client,是:
// In client mode, launch the application main class directly// In addition, add the main application jar and any added jars (if any) to the classpathif (deployMode == CLIENT) childMainClass = args.mainClass
即,client类就是用户定义的主类,直接开始运行主类即可。
二、提交应用
(一)yarn-cluster方式
我们先看一下yarn-cluster方式,由上面的分析可知,yarn-cluster使用的是org.apache.spark.deploy.yarn.Client这个类进行任务提交,先看一下流程图:
图片来自于spark技术内幕P84,下同。
先说一下总体的流程步骤:
======================================
步骤一:Client类提交应用到YARN ResourceManager,向RM申请资源作为AM
步骤二:在申请到的机器中启动driver,注册成为AM,并调用用户代码,然后创建SparkContext。(driver是一个逻辑概念,并不实际存在,通过抽象出driver这一层,所有的运行模式都可以说是在driver中调用用户代码了)
步骤三:SparkContext中创建DAGScheduler与YarnClusterScheduler与YarnClusterSchedulerBackend。当在用户代码中遇到action时,即会调用DAGScheduler的runJob,任务开始调度执行。
步骤四:YarnClusterSchedulerBackend在NodeManager上启动Executor
步骤五:Executor启动Task,开始执行任务
======================================
简单的说就是:
向RM申请资源建立driver——->在driver中执行用户代码,并创建AM——->遇到action时调用runJob——->开始调度、执行的过程了
因此3个比较复杂的流程分别为:
* 1、如何向YARN中申请资源,这涉及YARN的源码
* 2、如何调度,涉及DAGScheduler、YarnClusterScheduler与YarnClusterSchedulerBackend
* 3、如何执行任务,涉及Executor与Task。这3个部分会有专门的章节来讨论,我们这里先把整个流程理顺。
下面按按被调用的类来详细分析一下:
1、Client
Client类作为向YARN提交应用的客户端
步骤一:Client类提交应用到YARN ResourceManager,向RM申请资源作为 AM
(1)main函数
我们从main函数开始入手:
def main(argStrings: Array[String]) { ..... new Client(args, sparkConf).run() }
将不关键代码去掉后,就剩下一行,它调用run方法,继续看run方法
(2)run方法
好吧,它的主要内容也只是一行:
def run(): Unit = { val appId = submitApplication() ....... }
它调用了submitApplication方法。
(3)submitApplication方法
def submitApplication(): ApplicationId = { var appId: ApplicationId = null try { // Setup the credentials before doing anything else, // so we have don't have issues at any point. setupCredentials() yarnClient.init(yarnConf) yarnClient.start() // Get a new application from our RM val newApp = yarnClient.createApplication() val newAppResponse = newApp.getNewApplicationResponse() appId = newAppResponse.getApplicationId() // Verify whether the cluster has enough resources for our AM verifyClusterResources(newAppResponse) // Set up the appropriate contexts to launch our AM val containerContext = createContainerLaunchContext(newAppResponse) val appContext = createApplicationSubmissionContext(newApp, containerContext) // Finally, submit and monitor the application yarnClient.submitApplication(appContext) appId }
在submitApplication方法中,先对yarnClient进行了初始化,并从RM中申请到一个application,设置合适的AM(见下一点),最后就向RM提交应用了,并返回应用的ID。
(4)createContainerLaunchContext方法
上面在启动一个应用前,调用了createContainerLaunchContext方法,用于指定的appContext使用哪个AM:
val amClass = if (isClusterMode) { Utils.classForName("org.apache.spark.deploy.yarn.ApplicationMaster").getName } else { Utils.classForName("org.apache.spark.deploy.yarn.ExecutorLauncher").getName }
上面代码中指定了当yarn-cluster模式和yarn-client时,分别使用哪个类作为AM。
当向RM提交应用后,RM就会开始启动AM。YARN中启动AM的源码分析以后再补充。
步骤二:在申请到的机器中启动driver,注册成为AM,并调用用户代码,然后创建SparkContext。
2、ApplicationMaster
(1)main函数
当RM启动AM后,AM就开始执行main函数了
def main(args: Array[String]): Unit = { val amArgs = new ApplicationMasterArguments(args) SparkHadoopUtil.get.runAsSparkUser { () => master = new ApplicationMaster(amArgs, new YarnRMClient(amArgs)) System.exit(master.run()) } }
关键是调用了run方法,我们继续看run方法。
(2)run方法
先是设置了一些参数,并加载yarn的配置文件。然后设置了一些钩子
最后关键是执行了这2个方法:
if (isClusterMode) { runDriver(securityMgr) } else { runExecutorLauncher(securityMgr) }
分别对应yarn-cluster模式和yarn-client模式。
(3)runDriver方法
定义了如何启动driver,这也是yarn-cluster和yarn-client最大的区别,前者在yarn分配一台机器启动driver,并注册成为AM,而后者在本地上启动driver,再注册成为AM。
private def runDriver(securityMgr: SecurityManager): Unit = { addAmIpFilter() userClassThread = startUserApplication() // This a bit hacky, but we need to wait until the spark.driver.port property has // been set by the Thread executing the user class. val sc = waitForSparkContextInitialized() // If there is no SparkContext at this point, just fail the app. if (sc == null) { finish(FinalApplicationStatus.FAILED, ApplicationMaster.EXIT_SC_NOT_INITED, "Timed out waiting for SparkContext.") } else { rpcEnv = sc.env.rpcEnv val driverRef = runAMEndpoint( sc.getConf.get("spark.driver.host"), sc.getConf.get("spark.driver.port"), isClusterMode = true) registerAM(rpcEnv, driverRef, sc.ui.map(_.appUIAddress).getOrElse(""), securityMgr) userClassThread.join() } }
startUserApplication主要执行了调用用户的代码,以及创建了一个spark driver的进程。
Start the user class, which contains the spark driver, in a separate Thread.
registerAM向RM中正式注册AM。有了AM以后,用户代码就可以执行了,开始将任务切分、调度、执行。我们继续往下看。
然后,用户代码中的action会调用SparkContext的runJob,SparkContext中有很多个runJob,但最后都是调用DAGScheduler的runJob
步骤三:SparkContext中创建DAGScheduler与YarnClusterScheduler与YarnClusterSchedulerBackend
val (sched, ts) = SparkContext.createTaskScheduler(this, master)_schedulerBackend = sched _taskScheduler = ts_dagScheduler = new DAGScheduler(this)
然后调用DAGScheduler的runJob:
* Run a function on a given set of partitions in an RDD and pass the results to the given handler function. This is the main entry point for all actions in Spark.*
def runJob[T, U: ClassTag]( rdd: RDD[T], func: (TaskContext, Iterator[T]) => U, partitions: Seq[Int], resultHandler: (Int, U) => Unit): Unit = { if (stopped.get()) { throw new IllegalStateException("SparkContext has been shutdown") } val callSite = getCallSite val cleanedFunc = clean(func) logInfo("Starting job: " + callSite.shortForm) if (conf.getBoolean("spark.logLineage", false)) { logInfo("RDD's recursive dependencies:\n" + rdd.toDebugString) } dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, resultHandler, localProperties.get) progressBar.foreach(_.finishAll()) rdd.doCheckpoint() }
至此,应用就正式提交到集群准备运行了。
然后就开始DAGScheduler调用YarnClusterScheduler,YarnClusterScheduler调用YarnClusterSchedulerBackend,Executor启动Task开始执行任务的具体流程了。* 这些内容在之后的专题中详细分析。*
步骤四:YarnClusterSchedulerBackend在NodeManager上启动Executor
步骤五:Executor启动Task
(二)yarn-cluster方式
yarn-client的流程与yarn-cluster类似,主要区别在于它在本地运行driver,而cluster是在AM上运行driver。
先看一下流程图:
1、区别一:主类入口不同
如果是yarn-client,是:
// In client mode, launch the application main class directly// In addition, add the main application jar and any added jars (if any) to the classpathif (deployMode == CLIENT) childMainClass = args.mainClass
即,client类就是用户定义的主类,直接开始运行主类即可。
cluster是在专门的Client类中开始执行的,而yarn-client是在用户代码中开始执行的。
2、启动driver的方式不一样
client模式将在本机启动进程,并注册成为AM。
if (isClusterMode) { runDriver(securityMgr) } else { runExecutorLauncher(securityMgr) } private def runExecutorLauncher(securityMgr: SecurityManager): Unit = { val port = sparkConf.getInt("spark.yarn.am.port", 0) rpcEnv = RpcEnv.create("sparkYarnAM", Utils.localHostName, port, sparkConf, securityMgr) val driverRef = waitForSparkDriver() addAmIpFilter() registerAM(rpcEnv, driverRef, sparkConf.get("spark.driver.appUIAddress", ""), securityMgr) // In client mode the actor will stop the reporter thread. reporterThread.join() }
- spark提交应用的全流程分析
- spark提交应用的全流程分析
- spark的作业提交流程
- Spark源码分析之一:Job提交运行总流程概述
- IDE的使用,打包spark应用提交
- Spark on yarn 提交应用的方式
- spark之13:提交应用的方法(spark-submit)
- spark之13:提交应用的方法(spark-submit)
- Spark提交应用失败
- spark应用提交
- spark core源码分析6 Spark job的提交
- Spark源码走读(一) —— Spark应用提交流程
- spark代码提交流程(Standalone)
- spark 启动job的流程分析
- Spark应用运行流程
- iPhone应用提交流程
- Phone应用提交流程
- springMVC的全流程使用和分析
- 小微企业需要CRM客户关系管理软件吗?
- 使用JAVA Robot 扩展Webdriver 模拟键盘鼠标操作
- 阿里云 Ubuntu 14.04 安装mysql 5.6
- [iOS]关于__unsafe_unretained与__weak
- 用友ERP-供应链1-模块及价值
- spark提交应用的全流程分析
- LeetCode—318 Maximum Product of Word Lengths
- SSM回滚测试(给自己看)
- linux下如何手动更新Firefox
- OC语言 常用数据类型=>>NSData & NSMutableData
- 如何 解包 ,编辑 ,重新打包boot images
- CSocketClient.cpp
- 分布式哈希表DHT和一致性哈希
- 浅复制&深复制