这里是Yarn的Cluster模式,还有Yarn的Client模式以及StandAlone的Cluster和Client模式,这里先看最经典的;
Yarn-Cluster模式:
Cluster 模式将用于监控和调度的 Driver 模块启动在 Yarn 集群资源中执行。一般应用于实际生产环境。
1) 执行脚本提交任务,实际是启动一个 SparkSubmit 的 JVM 进程;
2) SparkSubmit 类中的 main 方法反射调用 YarnClusterApplication 的 main 方法;
3) YarnClusterApplication 创建 Yarn 客户端(Client类中封装的yarnClient对象),然后向 RM发送执行指令:bin/java ApplicationMaster;
4)随后 ResourceManager 分配 container,在合适的 NodeManager 上启动 ApplicationMaster
5)ApplicationMaster 启动 Driver 线程,执行用户的作业;
6) AM 向 RM 注册,申请资源;
7) 获取资源后 AM 向 NM 发送指令:bin/java YarnCoarseGrainedExecutorBackend;启动ExecutorBackend;
8)Executor启动后会向Driver进行反向注册,实例化Executor对象,等待任务;
9)Executor全部注册完成后Driver开始执行main函数;之后执行到 Action 算子时,触发一个 Job,并根据宽依赖开始划分 stage,每个 stage 生成对应的 TaskSet,之后将 task 分发到各个 Executor 上执行。

源码解析:
源码分析
1.1、SparkSubmit起点
def main(args: Array[String]): Unit = {
val submit = new SparkSubmit()
submit.doSubmit(args)
}
Spark-submit是一个提交程序;在main方法中val submit = new SparkSubmit()类;
submit.doSubmit(args) ; args为命令行参数;比如–master、–class等;
那么这些参数是如何被使用的呢?
doSubmit中有一个步骤appArgs = parseArguments(args);
parseArguments方法中只有一步,就是构建一个SparkSubmitArguments.Scala对象,也即上图中的橙色框内容;
SparkSubmitArguments对象中有一个方法parse(args.asJava),也即这个对象会把传入的参数进行解析,解析方式为正则表达式;会把传入的args拆分成opt和value两部分;也即指令和内容两部分,然后进行一个匹配操作(handle方法),完成操作的解析;
在appArgs = parseArguments(args)这一步下还有一个步骤:
val appArgs = parseArguments(args)
if (appArgs.verbose) {
logInfo(appArgs.toString)
}
appArgs.action match {
case SparkSubmitAction.SUBMIT => submit(appArgs, uninitLog)
case SparkSubmitAction.KILL => kill(appArgs)
case SparkSubmitAction.REQUEST_STATUS => requestStatus(appArgs)
case SparkSubmitAction.PRINT_VERSION => printVersion()
}
这里的action是从哪里得到的呢?
还是在SparkSubmitArguments.Scala类中有以下这么一个操作:
action = Option(action).getOrElse(SUBMIT) //第一次提交的时候,这里会返回一个Submit,也即appArgs.action = Submit;
//上面那一步自然会执行case SparkSubmitAction.SUBMIT => submit(appArgs, uninitLog) //开始提交;
//此时,这里的submit方法中会先做一个判断
if (args.isStandaloneCluster && args.useRest) {
......
}
else {
doRunMain()
}
//其中doRunMain()方法也有一个判断:
if (args.proxyUser != null) {
//命令行参数是否有代理用户;这里是没有传入的;
}
else {
runMain(args, uninitLog)
}
//进入runMain方法中:
private def runMain(args: SparkSubmitArguments, uninitLog: Boolean): Unit = {
val (childArgs, childClasspath, sparkConf, childMainClass) = prepareSubmitEnvironment(args) //注意,这里的 childMainClass非常重要;
// Let the main class re-initialize the logging system once it starts.
if (uninitLog) {
Logging.uninitialize()
}
//val (childArgs, childClasspath, sparkConf, childMainClass) = prepareSubmitEnvironment(args)非常重要
1.2、向Yarn提交应用
val loader = getSubmitClassLoader(sparkConf)
for (jar <- childClasspath) {
addJarToClasspath(jar, loader)
}
var mainClass: Class[_] = null
try {
mainClass = Utils.classForName(childMainClass) //反射机制;通过childMainClass类名获取类的信息;
} catch {
......
}
val app: SparkApplication = if (classOf[SparkApplication].isAssignableFrom(mainClass)) {
mainClass.getConstructor().newInstance().asInstanceOf[SparkApplication] //判断mainClass是否继承至SparkApplication类,如果继承了,则直接通过mainClass的构造器创建实例并转化为SparkApplication类;如果没有继承,则创建一个JavaMainApplication(mainClass)对象;
} else {
new JavaMainApplication(mainClass)
}
......
try {
app.start(childArgs.toArray, sparkConf) //不论是哪种结果,这里都会start对应的application;
} catch {
case t: Throwable =>
throw findCause(t)
}
//接下来分析childMainClass到底是什么;(实际上是之前提到的prepareSubmitEnvironment(args)方法的返回值)
接下来分析prepareSubmitEnvironment方法:
//prepareSubmitEnvironment方法:
//方法返回值是:(childArgs, childClasspath, sparkConf, childMainClass)
//找到对应的代码
val clusterManager: Int = args.master match {
//这里就是判断集群环境的代码;
case "yarn" => YARN
case m if m.startsWith("spark") => STANDALONE
case m if m.startsWith("mesos") => MESOS
case m if m.startsWith("k8s") => KUBERNETES
case m if m.startsWith("local") => LOCAL
case _ =>
error("Master must either be yarn or start with spark, mesos, k8s, or local")
-1
}
......
if (isYarnCluster) {
//714行
childMainClass = YARN_CLUSTER_SUBMIT_CLASS
//这个值实际上就是:YARN_CLUSTER_SUBMIT_CLASS =
"org.apache.spark.deploy.yarn.YarnClusterApplication"
......
}
//所以之前我们要找的childMainClass就是org.apache.spark.deploy.yarn.YarnClusterApplication;这里可以通过引入Yarn的依赖spark-yarn_2.12后查找:
//以下是查找到的代码
private[spark] class YarnClusterApplication extends SparkApplication {
override def start(args: Array[], : Unit = {
// SparkSubmit would use yarn cache to distribute files & jars in yarn mode,
// so remove them from sparkConf here for yarn mode.
conf.remove(JARS)
conf.remove(FILES)
new Client(new ClientArguments(args), conf, null).run() //这里会创建一个客户端对象;
}
}
//所以根据之前的代码mainClass.getConstructor().newInstance().asInstanceOf[SparkApplication]会创建一个YarnClusterApplication对象;(即图中下部橙框部分内容)

1.3、Yarn集群分析:
//继续衔接刚才的代码:
//Client类中有一个非常重要的变量:
private val yarnClient = YarnClient.createYarnClient
//进入YarnClient.createYarnClient方法中:
public static YarnClient createYarnClient() {
YarnClient client = new YarnClientImpl();

本文深入剖析了Spark在YARN集群模式下的工作流程,从SparkSubmit开始,详细讲解了如何通过YarnClusterApplication提交应用,如何启动ApplicationMaster和Driver线程,以及Executor的注册和任务执行过程。源码分析涵盖了SparkSubmit、YarnClient、ApplicationMaster、ExecutorBackend等关键组件,展示了Spark与YARN交互的详细步骤。
最低0.47元/天 解锁文章
618

被折叠的 条评论
为什么被折叠?



