本节从应用部署的角度解析相关的源代码,主要包括脚本提交时对应JVM进程启动的主类org.apache.spark.launcher.Main、定义应用程序提交的行为类型的类org.apache.spark. deploy.SparkSubmitAction、应用程序封装底层集群管理器和部署模式的类org.apache.spark. deploy.SparkSubmit,以及代表一个应用程序的驱动程序的类org.apache.spark.SparkContext。
1.Main解析
从前面的脚本分析可以得出,最终都是通过org.apache.spark.launcher.Main类(下面简称Main类)来启动应用程序的,因此,首先来分析Main类。
在Main类的源代码中,类的注释如下。
对应在Main对象的入口方法main上的注释如下。
Main类主要有两种工作模式,分别描述如下。
(1)spark-submit
启动器要启动的类为"org.apache.spark.deploy.SparkSubmit"时,对应为"spark-sub-mit"工作模式。此时,使用SparkSubmitCommandBuilder类来构建启动命令。
(2)spark-class
启动器要启动的类是除SparkSubmit之外的其他类时,对应为"spark-class"工作模式。此时,使用SparkClassCommandBuilder类的buildCommand方法来构建启动命令。
以"spark-submit"工作模式为例,对应的在构建启动命令的SparkSubmitCommandBuilder类中,上述调用的SparkClassCommandBuilder构造函数定义如下。
从这里初步的参数解析可以看出,前面脚本中的参数与最终对应的主资源间的对应关系如表3-1所示。
表3-1 脚本中的参数与主资源间的对应关系
如果继续跟踪appResource赋值的源代码,可以跟踪到一些特殊类的类名与最终对应的主资源间的对应关系,部分如表3-2所示。
表3-2 特殊类的类名与主资源间的对应关系
有兴趣的话可以继续跟踪SparkClassCommandBuilder类的buildCommand方法的源代码,查看构建的命令具体有哪些。
通过Main类的简单解析,可以将前面的脚本分析结果与后面即将进行分析的SparkSub-mit类关联起来,以便进一步解析与应用程序提交相关的其他源代码。
从前面的脚本分析可以看到,在提交应用程序时,Main所启动的类,也就是用户最终提交执行的类是org.apache.spark.deploy.SparkSubmit。因此下面开始解析SparkSubmit相关的源代码,包括提交行为的定义、提交时的参数解析,以及最终提交运行的代码解析。
2.SparkSubmitAction解析
SparkSubmitAction定义了提交应用程序的行为类型,源代码如下。
从源代码中可以看到,分别定义了SUBMIT、KILL和REQUEST STATUS这3种行为类型,对应提交应用、停止应用和查询应用的状态。
3.SparkSubmit解析
SparkSubmit的全路径为org.apache.spark.deploy.SparkSubmit。从SparkSubmit类的注释可以看出,SparkSubmit是启动一个Spark应用程序的主入口点,这与前面从脚本分析得到的结论是一致的。首先来看SparkSubmit类的注释,如下所示。
SparkSubmit会帮助用户设置Spark相关依赖包的classpath设置,同时,为了帮助用户简化提交应用程序的复杂性,SparkSubmit提供了一个抽象层,封装了底层复杂的集群管理器与部署模式的各种差异点。即,通过SparkSubmit的封装,集群管理器与部署模式对用户而言是透明的。
SparkSubmit透明化(通过SparkSubmit的封装,集群管理器与部署模式对用户而言是透明的)的集群管理器定义的源代码如下。
被SparkSubmit透明化的部署模式定义的源代码如下。
作为提交应用程序的入口点,SparkSubmit中根据具体的集群管理器进行参数转换、参数校验等操作,比如对模式的检查,代码中给出了针对特定情况下不支持的集群管理器与部署模式,在这些模式下提交应用程序会直接报错退出。
首先,一个程序运行的入口点对应单例对象的main函数,因此在执行SparkSubmit时,对应的入口点是objectSparkSubmit的main函数,具体代码如下。
其中,第3行代码中的SparkSubmitArguments类对应用户调用提交脚本spark-submit时传入的参数信息。对应的脚本的帮助信息(./bin/spark-submit--help)也是由该类的printUsageAndExit方法提供的。
找到上面的入口点代码之后,就可以开始分析其内部的源代码。对应参数信息的Spark-SubmitArguments可以参考脚本的帮助信息来查看具体参数所对应的含义。参数分析之后,便是各种提交行为的具体处理。SparkSubmit支持SparkSubmitAction包含的3种行为,下面以行为SparkSubmitAction.SUBMIT为例进行分析,其他行为也可以从各自的具体处理代码进行分析。
对应处理SparkSubmitAction.SUBMIT行为的代码入口点为submit(appArgs),进入该方法,即进入提交应用程序的处理方法的具体代码如下。
其中,最终运行所需的参数都由prepareSubmitEnvironment方法负责解析和转换,然后根据其结果执行。解析的结果包含以下4部分。(www.xing528.com)
·子进程运行所需的参数。
·子进程运行时的classpath列表。
·系统属性的映射。
·子进程运行时的主类。
解析之后调用runMain方法,该方法中除了一些环境设置等操作外,最终会调用解析得到的childMainClass的main方法。下面简单分析一下prepareSubmitEnvironment方法,通过该方法来了解SparkSubmit是如何帮助底层的集群管理器和部署模式的封装。接下来以不同集群管理器和部署模式下最终运行的childMainClass类的解析为主线进行分析。
1)当部署模式为Client时,将childMainClass设置为传入的mainClass,对应代码如下。
2)当集群管理器为Standalone、部署模式为Cluster时,根据提交的两种方式将childMa-inClass分别设置为不同的类,同时将传入的args.mainClass(提交应用程序时所设置的主类)及其参数根据不同集群管理器与部署模式进行转换,并封装到新的主类所需的参数中。对应的设置如表3-3所示。
表3-3 Standalone+Cluster时两种不同提交方式下的childMainClass封装
表3-3中,REST(representational State Transfer,表述性状态传递)是Roy Fielding博士在2000年他的论文中提出来的一种软件架构网格。
这些设置的主类相当于封装了应用程序提交时的主类,运行后负责向Master结点申请启动提交的应用程序。
3)当集群管理器为YARN、部署模式为Cluster时,childMainClass及对应的mainClass的设置如表3-4所示。
表3-4 YARN+Cluster时childMainClass下的childMainClass封装
4)当集群管理器为Mesos、部署模式为Cluster时,childMainClass及对应的mainClass的设置如表3-5所示。
表3-5 Mesos+Cluster时childMainClass下的childMainClass封装
从上面的分析可以看到,当使用Clinet部署模式进行提交时,由于设置的childMainClass为应用程序提交时的主类,因此是直接在提交点执行设置的主类,即mainClass。当使用Cluster部署模式进行提交时,则会根据具体集群管理器等信息使用相应的封装类。这些封装类会向集群申请提交应用程序的请求,然后在由集群调度分配得到的结点上启动所申请的应用程序。
以将封装类设置为"org.apache.spark.deploy.Client"为例,从该类主入口main方法查看,可以看到构建了一个ClientEndpoint实例,在构建该实例时,会将提交应用程序时设置的mainClass等信息封装到DriverDescription实例中,然后发送到Master,申请执行用户提交的应用程序。
对应各种集群管理器与部署模式的组合,实际代码中的处理细节非常多。这里仅仅给出了一种源代码阅读的方式,和对应的大数据处理一样,通常采用化繁为简的方式去阅读复杂的源代码。比如这里在理解了整个大框架的调用过程后,以childMainClass的设置作为主线去解读源代码,相应的,在扩展阅读其他源代码时,也可以采用这种方式,以某种集群管理器与部署模式为主线,详细阅读相关的代码。最后,在了解各种组合的处理细节之后,通过对比、抽象等方法,对整个SparkSubmit进行归纳总结即可。
参考3.4.1部署框架一章中的集群部署组件图(图3-2),可以看到提交的应用程序的驱动程序(Driver Program)部分对应包含了一个SparkContext实例。因此接下来从该实例出发,解析驱动程序在不同的集群管理器的部署细节。
4.SparkContext解析
在详细解析SparkContext实例之前,首先来看下SparkContext类的注释部分,具体如下。
SparkContext类是Spark功能的主入口点。一个SparkContext实例代表了与一个Spark集群的连接,并且通过该实例可以在集群中构建RDD、累加器及广播变量。SparkContext实例的构建参数config,描述了应用程序的Spark配置。在该参数中指定的配置属性会覆盖默认的配置属性及系统属性。
在SparkContext类文件中,定义了一个描述集群管理器类型的单例对象SparkMasterRe-gex,在该对象中详细给出了当前Spark所支持的各种集群管理器类型,具体代码如下。
在SparkContext类中的主要流程可以归纳如下。
1)createSparkEnv:创建Spark的执行环境对应的SparkEnv实例。
对应代码如下。
2)createTaskScheduler:创建作业调度器实例。
对应代码如下。
其中,TaskScheduler是低层次的任务调度器,负责任务的调度。通过该接口提供可插拔的任务调度器。每个TaskScheduler负责调度一个SparkContext实例中的任务,负责调度上层DAG调度器中每个Stage提交的任务集(TaskSet),并将这些任务提交到集群中运行,在任务提交执行时可以使用失败重试机制设置失败重试的次数。上述对应高层的DAG调度器的实例构建请参见下一步。
3)newDAGScheduler:创建高层Stage调度的DAG调度器实例。
对应代码如下。
DAGScheduler是高层调度模块,负责作业(Job)的Stage拆分,以及最终将Stage对应的任务集提交到低层次的任务调度器上。
下面基于这些主要流程,针对SparkMasterRegex单例对象中给出的各种集群部署模式进行解析。对应不同集群模式下,这些流程中构建了包括TaskScheduler与SchedulerBackend的不同的具体子类,所构建的相关实例具体如表3-6所示。
表3-6 各种情况下TaskScheduler与SchedulerBackend的不同的具体子类
与TaskScheduler与SchedulerBackend不同的是,在不同集群模式中,应用程序的高层调度器DAGScheduler的实例是相同的,即对应在Spark on YARN与Mesos等集群管理器中,应用程序内部的高层Stage调度是相同的。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。