文章目录
前言:
学习完spark之后,其实只是大致懂了怎么去用这个框架,但是对于它是怎么运行的还是一无所知,所以为了更加清楚的了解框架底层到底是怎么实现和运行的,我们需要跟踪源码看看它底层是怎么实现的
花费很多精力才追完源码,看完觉得有帮助的朋友三连哦,请不要下次一定😜
一 spark应用运行流程图示
步骤:
1 spark向yarn提交运行指令 bin/java ApplicationMaster
2 yarn会在某个NodeManager启动ApplicationMaster
3 AM启动Driver线程,执行用户类的main方法
4 AM向RM进行注册、申请资源
5 RM向AM返回可用资源,AM分配资源
6 AM根据资源访问NM,并启动Executor
6.1 Executor启动后会注册通信,并收到消息
6.2 收到消息后会执行通信对象的onStart方法
7 Executor向driver反向注册
8 Driver向Executor反馈消息,注册完毕
8.1Executor接收消息之后,创建计算对象,等待任务的执行
9 Driver进行任务的划分并发送给Executor进行计算
二 spark应用运行流程源码分析
我们一般都是基于yarn cluster模式来进行部署,我们也就按照这个模式来进行追踪源码
之前在我们配好yarn模式之后,我们根据官方文档给的案例进行一次测试
bin/spark-submit
--class org.apache.spark.examples.SparkPi
--master yarn
--deploy-mode cluster
./examples/jars/spark-examples_2.11-2.1.1.jar 10
当时我们并不知道为什么要这样去写,我们去查看spark-submit脚本
exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"
发现其实就是去调用org.apache.spark.deploy.SparkSubmit这个类
1 提交参数的封装
我们去查看SparkSubmit这个类的源码
def main(args: Array[String]): Unit = {
val submit = new SparkSubmit()
submit.doSubmit(args)
}
在main方法中创建了一个SparkSubmit对象,对象调用了doSubmit方法,传的参数就是我们写的那些参数
val appArgs = parseArguments(args) //解析参数
在doSubmit方法中,我们看到这个方法,看的出是一个解析参数的方法,我们再进去看看是怎么进行参数的解析的
protected def parseArguments(args: Array[String]): SparkSubmitArguments = {
new SparkSubmitArguments(args)
}
我们进入到SparkSubmitArguments类中,发现有很多变量和方法用来封装参数,有一个handle方法,就是处理参数的方法了
protected final String CLASS = "--class";
protected final String CONF = "--conf";
protected final String DEPLOY_MODE = "--deploy-mode";
protected final String MASTER = "--master";
protected final String NAME = "--name";
override protected def handle(opt: String, value: String): Boolean = {
opt match {
case NAME =>
name = value
case MASTER =>
master = value
case CLASS =>
mainClass = value
case DEPLOY_MODE =>
if (value != "client" && value != "cluster") {
error("--deploy-mode must be either \"client\" or \"cluster\"")
}
deployMode = value
//下面还有很多,我们就只看比较熟悉的几个
参数解析完成之后,我们回退到进入解析参数方法的那里
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里面看看
var action: SparkSubmitAction = null
action = Option(action).getOrElse(SUBMIT)
我们可以看到,如果action为空的时候,默认会给它一个值SUBMIT,此时我们知道是调用这行代码执行
case SparkSubmitAction.SUBMIT => submit(appArgs, uninitLog)
2 反射创建对象
进入submit方法之后,我们去掉一些暂时不关心的代码
private def submit(args: SparkSubmitArguments, uninitLog: Boolean): Unit = {
def doRunMain(): Unit = {
if (args.proxyUser != null) {
val proxyUser = UserGroupInformation.createProxyUser(args.proxyUser,
UserGroupInformation.getCurrentUser())
} else {
runMain(args, uninitLog)
}
}
我们可以发现,最后其实是调用runMain方法
//这是在准备提交的环境
val (childArgs, childClasspath, sparkConf, childMainClass) = prepareSubmitEnvironment(args)
//获取提交的类加载器
val loader = getSubmitClassLoader(sparkConf)
//将我们需要的jar包放到classPath下
for (jar <- childClasspath) {
addJarToClasspath(jar, loader)
}
//通过类名加载类,获取类信息
mainClass = Utils.classForName(childMainClass)
val app: SparkApplication = if(classOf[SparkApplication].isAssignableFrom(mainClass)) {
//通过反射创建对象并进行强转为SparkApplication类型
mainClass.getConstructor().newInstance().asInstanceOf[SparkApplication]
} else {
new JavaMainApplication(mainClass)
}
//启动app对象
app.start(childArgs.toArray, sparkConf)
通过上面的分析,我们知道会创建一个APP对象,但是我们还不知道具体是创建哪个类的对象,但是我们可以知道是通过prepareSubmitEnvironment方法获取的childMainClass
if