Spark作业基本运行原理解析!

浏览: 2549

1、基本原理

Spark作业的运行基本原理如下图所示:

我们使用spark-submit提交一个Spark作业之后,这个作业就会启动一个对应的Driver进程。提交作业的节点称为Master节点,Driver进程就是开始执行你Spark程序的那个Main函数(Driver进程不一定在Master节点上)。根据你使用的部署模式(deploy-mode)不同,Driver进程可能在本地启动,也可能在集群中某个工作节点上启动。

Driver进程本身会根据我们设置的参数,占有一定数量的内存和CPU core。而Driver进程要做的第一件事情,就是向集群管理器申请运行Spark作业需要使用的资源,这里的资源指的就是Executor进程。YARN集群管理器会根据我们为Spark作业设置的资源参数,在各个工作节点Worker上,启动一定数量的Executor进程,每个Executor进程都占有一定数量的内存和CPU core。

在申请到了作业执行所需的资源之后,Driver进程就会开始调度和执行我们编写的作业代码了。Driver进程会将我们编写的Spark作业代码分拆为多个stage,每个stage执行一部分代码片段,并为每个stage创建一批task,然后将这些task分配到各个Executor进程中执行。task是最小的计算单元,负责执行一模一样的计算逻辑(也就是我们自己编写的某个代码片段),只是每个task处理的数据不同而已。一个stage的所有task都执行完毕之后,会在各个节点本地的磁盘文件中写入计算中间结果,然后Driver就会调度运行下一个stage。下一个stage的task的输入数据就是上一个stage输出的中间结果。如此循环往复,直到将我们自己编写的代码逻辑全部执行完,并且计算完所有的数据,得到我们想要的结果为止。

有了一个整体的认识,我们来看一下具体的细节。

2、过程细节

2.1 部署模式

部署模式通常有两种,client模式和cluster模式。

client模式

client模式的示意图如下:

对于client模式来说,需要注意的有以下几点:
1、client mode下Driver进程运行在Master节点上,不在Worker节点上,所以相对于参与实际计算的Worker集群而言,Driver就相当于是一个第三方的“client”
2、正由于Driver进程不在Worker节点上,所以其是独立的,不会消耗Worker集群的资源
3、client mode下Master和Worker节点必须处于同一片局域网内,因为Drive要和Executorr通信,例如Drive需要将Jar包通过Netty HTTP分发到Executor,Driver要给Executor分配任务等
4、client mode下没有监督重启机制,Driver进程如果挂了,需要额外的程序重启

cluster模式

cluster模式示意图如下:

对于cluster模式来说,需要注意以下几点:
1、Driver程序在worker集群中某个节点,而非Master节点,但是这个节点由Master指定
2、Driver程序占据Worker的资源
3、cluster mode下Master可以使用–supervise对Driver进行监控,如果Driver挂了可以自动重启
4、cluster mode下Master节点和Worker节点一般不在同一局域网,因此就无法将Jar包分发到各个Worker,所以cluster mode要求必须提前把Jar包放到各个Worker几点对应的目录下面

一般来说,如果提交任务的节点Master和工作集群Workers在同一个网段内,此时client模式比较合适,因为此时Driver进程不会占用集群资源,但是如果提交任务的节点和Worker集群相隔较远,就会采用cluster model来最小化Driver和Executer之间的网络延迟。

2.2 RDD的结构及task的划分

一个RDD在物理上被切分为多个Partition,即数据分区,这些Partition可以分布在不同的节点上。Partition是Spark计算任务的基本处理单位,决定了并行计算的粒度,而Partition中的每一条Record为基本处理对象。而一个task用于计算RDD中的一个Partition,如下图所示:

上图显示了一个groupByKey算子,左侧的RDD中有3个Partition,右侧的RDD有两个Partition。因此在进行groupByKey操作时,会产生两个Task。

2.3 宽依赖和窄依赖

在Spark的任务中,一个RDD转换成另一个RDD的过程中,涉及到宽窄依赖的问题,在宽依赖的地方会形成数据的shuffle并划分stage,因此我们先来讲一下宽窄依赖问题。

窄依赖

窄依赖是RDD中最常见的依赖关系,用于表示每一个父RDD的一个Partition最多被子RDD的1个Partition所使用。如下图都是窄依赖:

窄依赖可以分为两类,一对一的依赖关系和范围依赖关系,如果子RDD最多依赖于1个父RDD,那么就是一对一的依赖关系,如上图中的map,filter和与协同分区的输入值进行join操作。如果子RDD依赖于多个父RDD,那么就是范围依赖关系,如上图的union。

宽依赖

宽依赖表示一个父RDD的Partition会被多个子RDD的Partition使用,如下图:

宽依赖中会出现数据Shuffle操作,因此也就有了Stage的划分。

2.4 RDD的算子

在RDD中有两种算子:transformation和action算子。

Transformation算子

Transformation算子会由一个RDD生成一个新的 RDD。Transformation算子是延迟计算的,也就是说从一个RDD转换生成另一个RDD的转换操作不是马上执行,需要等到Actions算子时,才真正开始运算。常见的Transformation算子如下:

Action算子

Action算子会对 RDD 计算出一个结果,并把结果返回到驱动器程序中,或把结果存储到外部存储系统(如 HDFS)中,常见的Action算子如下:

2.5 Job和Stage的划分

介绍了RDD中的宽窄依赖以及两种算子,接下来介绍一下Job和Stage的划分。每一次的Action算子都会产生一个Job,每一个Job中根据RDD之间的宽窄依赖关系产生多个Stage。我们重点来介绍一下Stage的划分。

Stage划分

Stage划分的重点是RDD之间的宽依赖关系,如下图所示,产生G的过程中划分了3个Stage。

A和B之间是groupBy操作,由于B中每一个Partition依赖于A中多个Partition,因此是宽依赖,产生shuffle。A和B处于不同的Stage。B到G是窄依赖,不会划分Stage。C到D,D到F,E到F都是窄依赖,它们在一个Stage中。F到G是宽依赖,产生Shuffle,因此F和G划分为两个Stage,因此上图中总共有3个Stage存在。

每个Stage中又有多个task,这主要是根据RDD的partition决定的,上文中已经说到过了,这里不再赘述。

2.6 其他重要概念

我们用下面的图来感受一下:

Application

用户自己写的Spark应用程序,批处理作业的集合。Application的main方法为应用程序的入口,用户通过Spark的API,定义了RDD和对RDD的操作。

Driver 和 Executer

Spark在执行每个Application的过程中会启动Driver和Executor两种JVM进程:
Driver进程为主控进程,负责执行用户Application中的main方法,提交Job,并将Job转化为Task,在各个Executor进程间协调Task的调度。而运行在Worker上的Executor进程负责执行Task,并将结果返回给Driver,同时为需要缓存的RDD提供存储功能。

Master和Worker

整个集群分为 Master 节点和 Worker 节点,相当于 Hadoop 的 Master 和 Slave 节点。Master 节点上常驻 Master 守护进程,负责管理全部的 Worker 节点。Worker 节点上常驻 Worker 守护进程,负责与 Master 节点通信并管理 executors。

本文仅仅提供一个简单的Spark作业运行原理解析,更多复杂的知识,大家可以查阅更多的资料进行深入理解!

参考文献

https://tech.meituan.com/spark-tuning-basic.html
https://blog.csdn.net/mahuacai/article/details/51919615
https://spark-internals.books.yourtion.com/markdown/1-Overview.html
https://www.jianshu.com/p/e41b18a7e202

推荐 2
本文由 石晓文 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册