Spark核心组件

关于Spark的基本架构模块,在【Spark】一起了解一下大数据必不可少的Spark吧! 这篇文章已经有所提及,这里主要要先明白两个主要运行角色DriverExecutor在整个部署流程的主要职责

Driver

是Spark驱动器节点,主要用于执行Spark任务中的main方法,负责实际代码的执行。

详细职责:

1.将用户程序转化为作业(job)

2.在Executor之间调度任务(task)

3.跟踪Executor的执行情况

4.通过UI展示查询运行情况

Executor

其实Executor并不是一个进程,而是ExecutorBackend是一个进程,Executor是它的执行类,负责Spark作业中运行具体的任务,任务之间是彼此相互独立的

Spark应用启动时,Executor节点同时启动,并且始终伴随整个Spark应用的生命周期而存在。如果Executor节点发生故障或者崩溃,Spark会将故障节点的任务调度到其他Executor节点上执行。

详细职责:

1.运行组成Spark应用的任务,并将结果返回给驱动器进程

2.通过自身的块管理器(Block Manager) 为用户程序中要求缓存的RDD提供内存式存储。RDD 是直接缓存在Executor进程内的,因此任务可以在运行时充分利用缓存数据加速运算。


Spark通用运行流程图


Standalone模式运行机制

Client模式流程图

Cluster模式流程图


On-Yarn模式运行机制

Client模式流程图



注:

1.ApplicationMaster是Yarn集群中的核心概念,任何要在Yarn集群上启动的Job类型(包括MR和Spark)都必须有一个ApplicationMaster。

2.每种计算框架(比如MR和Spark)如果想要在Yarn上执行自己的Application,就必须自己实现和提供一个ApplicationMaster,相当于自己实现了Yarn提供的接口,Spark自己开发的一个类。

3.Spark-On-Yarn在Client模式下,Application向ResourceManager申请资源和task的调度是分离的。

Cluster模式流程图


源码解读(多图版)

Spark On-Yarn Cluster模式示例

1.SparkSubmit

通过spark-submit提交任务,会在后台启动一个进程



然后就会调用spark-submit中的main方法

查看源码,在SparkSubmit的伴生对象中,我们可以看到其中要调用的main方法



代码如下

  1. override def main(args: Array[String]): Unit = {
  2. val appArgs = new SparkSubmitArguments(args)
  3. if (appArgs.verbose) {
  4. // scalastyle:off println
  5. printStream.println(appArgs)
  6. // scalastyle:on println
  7. }
  8. appArgs.action match {
  9. case SparkSubmitAction.SUBMIT => submit(appArgs)
  10. case SparkSubmitAction.KILL => kill(appArgs)
  11. case SparkSubmitAction.REQUEST_STATUS => requestStatus(appArgs)
  12. }
  13. }

从以上代码,可以看到val appArgs = new SparkSubmitArguments(args),这表示它将参数都封装到一起,而args指的就是提交任务时输入的--master--deploy-mode--executor-memory等这些参数。

具体有哪些参数,我们也可以点进去查看,以下就是里面封装的参数



那么究竟是如何给这些参数,赋值的

其实它是将这些参数解析转换成了java。



并且其中有一个handle方法,对我们输入的参数进行模式匹配。



再回到SparkSubmit中,我们可以看到一个关于action的模式匹配



action就和master一样,是我们上传jar包是需要配置的,如果不配置,它有方法默认为Submit





那么就可以看一下submit方法里面具体是什么,首先就可以看到一个prepareSubmitEnvironment,也就是准备上传环境,又是采用了一个模式匹配,返回值就是括号里那四个childArgs、childClasspath、sysProps、childMainClass



接着往下看,可以看到一个判断是否为standalone的Cluster模式,如果不是就会执行doRunMain方法



doRunMain方法中主要是在判断代理用户参数是否为空,也就是有没有给代理用户的参数,不过无论是否给了这个参数,最终都会运行runMain方法,而runMain方法中需要的函数正是之前准备上传环境所返回的值。



点进去查看runMain方法,最主要的就是这个反射加载类



同样,还有这个查找提交的jar包中是否有main方法,并作了一个是否有static修饰符的判断,其实就是在查找main方法invoke就是在调用main方法



现在了解到这,就需要知道反射加载类中的childMainClass是什么,它的首次出现,是作为prepareSubmitEnvironment返回的元祖中的最后一个元素。



那么就要弄明白,childMainClass是如何进行赋值的。

在prepareSubmitEnvironment方法中,是经过多个判断方法,判断当前是什么运行模式,来对childMainClass进行赋值,第一张图为yarn模式的Cluster模式,第二张是Client模式。Cluster给childMainClass赋值为 “org.apache.spark.deploy.yarn.Client”,Client则是args.mainClass,也就是启动时输入的--class的命令行参数(比如org.apache.spark.examples.SparkPi)。





实际工作中,一般用的都是yarn-Cluster模式,所以现在要弄清楚 “org.apache.spark.deploy.yarn.Client” 是什么,在这之前,现在pom.xml中添加以下包

  1. <dependency>
  2. <groupId>org.apache.spark</groupId>
  3. <artifactId>spark-yarn_2.11</artifactId>
  4. <version>2.1.1</version>
  5. </dependency>

查找类后,找到其中的main方法,那么我们发现,已经不再是SparkSubmit类了,已经到了Client类

2.Client

Client类的第一步同样也是封装参数





第二步就是new了一个Client对象。



我们需要看看这么做的原因,由下图就能知道,它首先就是想创建一个YarnClient



创建成功后,它立即进行了run方法,run方法点进去,第一件事就是submitApplication,提交应用,并且把最后返回的值赋值为appID,这就是在spark-shell界面登录时看到的appID。



submitApplication方法中,最重要的一步就是yarnClient.submitApplication(appContext),作用是向Yarn提交应用,它的参数是appContext,往上推,appContext的是createApplicationSubmissionContext方法返回的值,而此方法需要的参数containerContextcreateContainerLaunchContext方法返回的值,这两个方法的作用是设置适当的context启动ApplicationMaster,接下来的重点就是看这两个方法。



点进createContainerLaunchContext方法,可以看到的submitApplication提交的不是真实的应用,而是提交的command——指令。command中封装的指令,根据上面args的判断后赋值,可以确定有client和cluster两种模式,cluster模式的指令为 /bin/java org.apache.spark.deploy.yarn.ApplicationMaster,client模式的指令为org.apache.spark.deploy.yarn.ExecutorLauncher

3.ApplicationMaster

现在我们就可以来看ApplicationMaster这个类,查看它的main方法。



在这之前,首先要搞明白,client模式下org.apache.spark.deploy.yarn.ExecutorLauncher和ApplicationMaster有什么区别,所以先看一下ExecutorLauncher类,发现它和ApplicationMaster其实没有区别,甚至直接调用了ApplicationMaster的Main方法。



好,现在回到ApplicationMaster的main方法,第一步还是对参数进行了封装第二步就是new了一个ApplicationMaster,并且可以看到里面有两个参数,第一个amArgs就是第一步封装的参数,第二个new YarnRMClient是创建了一个yarn的ResourceManager的客户端,这么做是因为ApplicationMaster会向ResourceManager申请资源进行交互。第三步就是让ApplicationMaster运行起来



先点进ApplicationMaster看一下,在这里,我们需要明白两个东西,一个是RPC,一个是Endpoint,其实很简单,RPC就是Remote Procedure Call——远程过程调用,主要是通过网络从远程计算机程序上请求服务,而Endpoint就是终端

这里rpcEnv中的Env就是EnvironmentamEndpoint是ApplicationMaster的终端。



接下来,进入到run方法,里面最重要的就是判断是否是cluster模式,如果是cluster模式,执行runDriver方法,如果不是,执行runExecutorLauncher方法



那就该进入到runDriver方法,第一个就是startUserApplication方法,顾名思义,启动用户应用,点进去,可以看到,userClassLoader.loadClass(args.userClass).getMethod(“main”, classOf[Array[String]]),这一步主要就是要获取我们输入命令行参数时--class中类的main方法





获取到用户类的main方法,下一步当然就是执行,它new了一个Thread,进行启动,我们可以看到它的名字就是Driver,所以Driver是一个线程,我们写的SparkContext、StreamingContext,就是Driver。



ApplicationMaster启动后,ResourceManager要和AM进行交互,所以要registerAM,它们两个都是进程,所以我们看到了参数中有RPC。

点进去registerAM,可以看到有一个client的注册,这个client就是YarnRMClient,所以这个过程就是ApplicationMaster向ResourceManager申请资源的过程







那么,究竟是怎么分配资源的呢?我们可以看allocateResources这个方法。

点进去可以看到,主要就是分配RM中的container,allocatedContainers.size > 0就意味着RM中有资源可以分配,接下来就会进行资源的处理——handleAllocatedContainers





点进去handleAllocatedContainers,首先进行一个节点分配的操作,然后开始运行——runAllocatedContainers

点进去可以看到,最终的目的就是启动Executor——new ExecutorRunnable().run





ApplicationMaster申请资源完毕后,通知NodeManager启动Container,而startContainer中又出现了prepareComand,command就是 /bin/java "org.apache.spark.executor.CoarseGrainedExecutorBackend"





4.CoarseGrainedExecutorBackend

打开CoarseGrainedExecutorBackend类,找到它的main方法,直接看其中的run方法,它又在run些什么呢?



点进去我们发现,Executor其实是ExecutorBackend的一个计算对象,真正运行的进程应该是ExecutorBackend





这里继承了一个ThreadSafeRpcEndpoint,它又继承自RpcEndpoint,RpcEndpoint类中讲到,一个Endpoint的生命周期就是constructor -> onStart -> receive* -> onStop







ExecutorBackend继承自它,那说明ExecutorBackend也是一个进程,它也有同样的生命周期。

回到CoarseGrainedExecutorBackend类中,我们看到它确实有,在onStart阶段,它主要干了一件事,ref.ask[Boolean](RegisterExecutor——这就是前面一直说的Executor启动后会向Driver进行反向注册。在receive阶段,它干的就是上面说的new Executor,这么做就是创建了一个计算对象,来处理拿到的数据。


源码解读(无图版)

  1. 1. SparkSubmit
  2. // 启动进程
  3. -- main
  4. // 封装参数
  5. -- new SparkSubmitArguments
  6. // 提交
  7. -- submit
  8. // 准备提交环境
  9. -- prepareSubmitEnvironment
  10. // Cluster
  11. -- childMainClass = "org.apache.spark.deploy.yarn.Client"
  12. // Client
  13. -- childMainClass = args.mainClass (SparkPi)
  14. -- doRunMain (runMain)
  15. // 反射加载类
  16. -- Utils.classForName(childMainClass)
  17. // 查找main方法
  18. -- mainClass.getMethod("main", new Array[String](0).getClass)
  19. // 调用main方法
  20. -- mainMethod.invoke
  1. 2. Client
  2. -- main
  3. -- new ClientArguments(argStrings)
  4. -- new Client
  5. -- yarnClient = YarnClient.createYarnClient
  6. -- client.run
  7. -- submitApplication
  8. // 封装指令 command = bin/java org.apache.spark.deploy.yarn.ApplicationMaster (Cluster)
  9. // command = bin/java org.apache.spark.deploy.yarn.ExecutorLauncher (client)
  10. -- createContainerLaunchContext
  11. -- createApplicationSubmissionContext
  12. // 向Yarn提交应用,提交指令
  13. -- yarnClient.submitApplication(appContext)
  1. 3. ApplicationMaster
  2. // 启动进程
  3. -- main
  4. -- new ApplicationMasterArguments(args)
  5. // 创建应用管理器对象
  6. -- new ApplicationMaster(amArgs, new YarnRMClient)
  7. // 运行
  8. -- master.run
  9. // Cluster
  10. -- runDriver
  11. // 启动用户应用
  12. -- startUserApplication
  13. // 获取用户应用的类的main方法
  14. -- userClassLoader.loadClass(args.userClass)
  15. .getMethod("main", classOf[Array[String]])
  16. // 启动Driver线程,执行用户类的main方法,
  17. -- new Thread().start()
  18. // 注册AM
  19. -- registerAM
  20. // 获取yarn资源
  21. -- client.register
  22. // 分配资源
  23. -- allocator.allocateResources()
  24. // 处理资源
  25. -- handleAllocatedContainers
  26. // 启动container
  27. -- runAllocatedContainers
  28. -- new ExecutorRunnable().run
  29. -- startContainer
  30. // command = bin/java org.apache.spark.executor.CoarseGrainedExecutorBackend
  31. -- prepareCommand
  1. 4. CoarseGrainedExecutorBackend
  2. // 启动进程
  3. -- main
  4. -- run
  5. -- onStart
  6. // 反向注册
  7. -- ref.ask[Boolean](RegisterExecutor
  8. -- receive
  9. -- case RegisteredExecutor
  10. // 创建计算对象
  11. -- new Executor

【Spark】部署流程的深度了解的更多相关文章

  1. Spark部署三种方式介绍:YARN模式、Standalone模式、HA模式

    参考自:Spark部署三种方式介绍:YARN模式.Standalone模式.HA模式http://www.aboutyun.com/forum.php?mod=viewthread&tid=7 ...

  2. Liferay7 BPM门户开发之45: 集成Activiti文件上传部署流程BPMN模型

    开发文件上传,部署流程模板. 首先,开发jsp页面,deploy.jsp <%@ include file="/init.jsp" %> <h3>${RET ...

  3. Jenkins环境拓扑及部署流程

    环境拓扑图: 部署流程:

  4. Activiti 部署流程定义及相关的表(classpath部署、zip部署)

    package com.mycom.processDefinition; import org.activiti.engine.ProcessEngine; import org.activiti.e ...

  5. OpenStack Keystone安装部署流程

    之前介绍了OpenStack Swift的安装部署,采用的都是tempauth认证模式,今天就来介绍一个新的组件,名为Keystone. 1. 简介 本文将详细描述Keystone的安装部署流程,并给 ...

  6. OpenStack Swift集群部署流程与简单使用

    之前介绍了<OpenStack Swift All In One安装部署流程与简单使用>,那么接下来就说一说Swift集群部署吧. 1. 简介 本文档详细描述了使用两台PC部署一个小型Sw ...

  7. activiti自定义流程之整合(四):整合自定义表单部署流程定义

    综合前几篇博文内容,我想在整合这一部分中应该会有很多模块会跳过不讲,就如自定义表单的表单列表那一块,因为这些模块在整合的过程中都几乎没有什么改动,再多讲也是重复无用功. 正因为如此,在创建了流程模型之 ...

  8. activiti自定义流程之Spring整合activiti-modeler5.16实例(四):部署流程定义

    注:(1)环境搭建:activiti自定义流程之Spring整合activiti-modeler5.16实例(一):环境搭建        (2)创建流程模型:activiti自定义流程之Spring ...

  9. Kent Beck揭秘Facebook开发部署流程

    http://www.infoq.com/cn/news/2013/10/facebook-development-deployment Facebook是世界上最大的社交网站,有超过10亿用户每月至 ...

随机推荐

  1. Go语言讲解深拷贝与浅拷贝

    我们在开发中会经常的把一个变量复制给另一个变量,那么这个过程,可能是深浅拷贝,那么今天帮大家区分一下这两个拷贝的区别和具体的区别. 一.概念 1.深拷贝(Deep Copy): 拷贝的是数据本身,创造 ...

  2. three.js - 一个javascript 3D代码库

    这个项目的目的是用最简单的开发模式创建一个轻量级的3 d代码库,这个js库提供了canvas,svg,css3d和webgl这四种渲染方式. 下载地址: 下载地址:https://github.com ...

  3. J - Recommendations CodeForces - 1315D

    https://blog.csdn.net/w_udixixi/article/details/104479288 大意:n个数,每个数只能向上加,a[i]+1需要的时间是t[i],求使这n个数无重复 ...

  4. F - Distinct Numbers

    链接:https://atcoder.jp/contests/abc143/tasks/abc143_f 题解:开两个数组,其中一个arr用来保存每个元素出现的次数,同时再开一个数组crr用来保存出现 ...

  5. Python - 批量获取文件夹的大小输出为文件格式化保存

    很多时候,查看一个文件夹下的每个文件大小可以轻易的做到,因为文件后面就是文件尺寸,但是如果需要查看一个文件夹下面所有的文件夹对应的尺寸,就发现需要把鼠标放到对应的文件夹上,稍等片刻才会出结果. 有时候 ...

  6. Android | 教你如何在安卓上实现通用卡证识别,一键各种卡绑定

    目录 前言 通用卡证识别的应用场景 如何使用通用卡证识别服务 集成通用卡证识别服务的关键流程 开发实战 1 开发准备 1.1 在项目级gradle里添加华为maven仓 1.2 在应用级的build. ...

  7. Python冒泡排序算法及其优化

    冒泡排序 所谓冒泡,就是将元素两两之间进行比较,谁大就往后移动,直到将最大的元素排到最后面,接着再循环一趟,从头开始进行两两比较,而上一趟已经排好的那个元素就不用进行比较了.(图中排好序的元素标记为黄 ...

  8. golang实现并发爬虫一(单任务版本爬虫功能)

    目的是写一个golang并发爬虫版本的演化过程. 那么在演化之前,当然是先跑通一下单任务版本的架构. 正如人走路之前是一定要学会爬走一般. 首先看一下单任务版本的爬虫架构,如下: 这是单任务版本爬虫的 ...

  9. 负载均衡服务之HAProxy基础配置(五)

    前文我们聊了下haproxy的修改报文首部的配置.压缩功能以及haproxy基于http协议自定义健康状态检测机制:回顾请参考https://www.cnblogs.com/qiuhom-1874/p ...

  10. Metasploit学习笔记(一)

    1.更新 apt-get update:更新源 apt-get upgrade:更新软件包 apt-get dist-upgrade:升级系统 2. Metasploit基础 2.1专业名词 Auxi ...