Spark运行架构
一、基本概念
RDD:是Resillient Distributed Dataset(弹性分布式数据集)的简称,是分布式内存的一个抽象概念,提供了一种高度受限的共享内存模型
数据从硬盘读取后封装为RDD,即从硬盘读取数据后存放在分布式内存中(可能跨节点内存)。一个RDD可包含多个分区。RDD分区中的数据可放在不同的HDFS数据节点来处理。
高度受限的共享内存模型:RDD是只读数据分区的集合,只能基于稳定的物理存储中的数据来创建RDD,或通过其他RDD执行转换操作(如map、join、groupby)来创建新的RDD。
DAG:是Directed Acyclic Graph(有向无环图)的简称,反映RDD之间的依赖关系
不同RDD之间通过各种转换操作和动作操作形成相互依赖关系,这些依赖关系就构成DAG
Executor:是运行在工作节点(WorkerNode)的一个作业进程,负责运行Task。采用多线程方式运行Task。
Application:用户编写的Spark应用程序,即作业程序
Task:运行在Executor上的工作单元,作业的多个任务
Job:一个Application(作业)包含多个Job。一个Job包含多个RDD及作用于这些RDD上的各种操作。
Spark中作业由多个RDD以及RDD之间的依赖关系和操作构成的。
Stage:一个Job会分为多组Task,每组Task被称为Stage,或者也被称为TaskSet。Stage是Job的基本调度单位。Stage代表了一组关联的、相互之间没有Shuffle依赖关系的任务组成的任务集
一个作业分解为多个Job,1个Job包括多个Stage(TaskSet,任务集),1个Stage包括多个Task。
二、架构设计
Spark运行架构包括集群资源管理器(Cluster Manager)、运行作业任务的工作节点(Worker Node)、每个应用的任务控制节点(Driver)和每个工作节点上负责具体任务的执行进程(Executor)
资源管理器可以自带或YARN。
每个作业由Driver生成DAG,然后由Driver对DAG分解由多个Executor来执行。
与Hadoop MapReduce计算框架相比,Spark所采用的Executor有两个优点:
一是利用多线程来执行具体的任务,减少任务的启动开销(下面图片实例中每个Executor由2个线程同时执行2个Task)
二是Executor中有一个BlockManager存储模块,会将内存和磁盘共同作为存储设备,有效减少IO开销

一个Application(作业)由一个Driver和若干个Job构成,一个Job由多个Stage构成,一个Stage由多个没有Shuffle关系的Task组成
当执行一个Application时,Driver会向集群管理器申请资源,启动Executor,并向Executor发送应用程序代码和文件,然后在Executor上执行Task,运行结束后,执行结果会返回给Driver,或者写到HDFS或者其他数据库中

三、Spark运行基本流程

(1)首先Driver为作业构建起基本的运行环境,即由Driver创建一个SparkContext,进行资源的申请、任务的分配和监控
(2)资源管理器为Executor分配资源,并启动Executor进程。Executor要实时向资源管理器汇报资源使用情况
(3)SparkContext(Spark上下文)根据RDD的依赖关系构建DAG图,DAG图提交给DAGScheduler(作业调度器)解析成Stage(即TaskSet,任务集),然后把一个个TaskSet提交给底层调度器TaskScheduler(任务调度器)处理;Executor向SparkContext申请Task,Task Scheduler将Task发放给Executor运行,并提供应用程序代码
(4)Task在Executor上运行,把执行结果反馈给TaskScheduler,然后反馈给DAGScheduler,运行完毕后写入数据并释放所有资源
总体而言,Spark运行架构具有以下特点:
(1)每个Application(作业)都有自己专属的Executor进程,并且该进程在Application运行期间一直驻留。Executor进程以多线程的方式运行Task
(2)Spark运行过程与资源管理器无关,只要能够获取Executor进程并保持通信即可
(3)Task采用了数据本地性(Task在靠近待处理数据的节点运行,即计算向数据靠拢)和推测执行(如果数据所在节点的资源已被占用不能运行新的Task,则推测是否可把数据迁移到其他节点来进行处理;如果迁移时间较长,则会等待数据所在节点资源释放足够时再运行Task)等优化机制
四、RDD运行原理
1、RDD设计背景
许多迭代式算法(比如机器学习、图算法等)和交互式数据挖掘工具,共同之处是,不同计算阶段之间会重用中间结果
目前的MapReduce框架都是把中间结果写入到HDFS中,带来了大量的数据复制、磁盘IO和序列化开销
RDD就是为了满足这种需求而出现的,它提供了一个抽象的数据架构,我们不必担心底层数据的分布式特性,只需将具体的应用逻辑表达为一系列转换处理,不同RDD之间的转换操作形成依赖关系(依赖关系构成DAG),可以实现管道化,避免中间数据存储
2、RDD概念
一个RDD就是一个分布式数据集合,本质上是一个只读的分区记录集合,每个RDD可分成多个分区,每个分区就是一个数据集片段,并且一个RDD的不同分区可以被保存到集群中不同的节点上,从而可以在集群中的不同节点上进行并行计算
RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,不能直接修改,只能基于稳定的物理存储中的数据集创建RDD,或者通过在其他RDD上执行确定的转换操作(如map、join和group by)而创建得到新的RDD
RDD提供了一组丰富的操作以支持常见的数据运算,分为“动作”(Action)和“转换”(Transformation)两种类型
RDD提供的转换接口都非常简单,都是类似map、join、groupBy、filter等粗粒度的数据转换操作,而不是针对某个数据项的细粒度修改(不适合网页爬虫)
表面上RDD的功能很受限、不够强大,实际上RDD已经被实践证明可以高效地表达许多框架的编程模型(比如MapReduce、SQL、Pregel)
Spark用Scala语言实现了RDD的API,程序员可以通过调用API实现对RDD的各种操作
RDD典型的执行过程如下:
(1)RDD读入外部数据源进行创建
(2)RDD经过一系列的转换(Transformation)操作,每一次都会产生不同的RDD,供给下一个转换操作使用
(3)最后一个RDD经过“动作”操作进行转换,并输出到外部数据源,或变成Scala集合或标量
这一系列处理称为一个Lineage(血缘关系,即父RDD转换生成子RDD),即DAG拓扑排序的结果
优点:惰性调用、管道化、避免同步等待、不需要保存中间结果、每次操作变得简单

惰性调用:RDD执行过程中,真正的计算发生在RDD最后的Action(动作)操作。对于所有的转换操作,Spark只记录转换操作形成的RDD间依赖关系,不会触发真正的计算。
3、RDD特性
Spark采用RDD以后能够实现高效计算的原因主要在于:
(1)高效的容错性
MapReduce容错机制:数据复制或者记录日志(节点故障可能需要从其他节点复制数据,开销大)
RDD容错机制:依据RDD间依赖关系(血缘关系)重新计算RDD丢失的分区(通过上一个RDD或HDFS源数据来生成损坏的RDD),无需回滚系统
(2)中间结果持久化到内存,数据在内存中的多个RDD操作之间进行传递,避免了不必要的读写磁盘开销
4、RDD之间的依赖关系

窄依赖:1个父RDD的1个分区只对应1个子RDD的1个分区;多个父RDD的分区可对应同一个子RDD同一个分区。即父子RDD分区间是一对一或多对一关系。一个父RDD分区只影响一个子RDD分区数据。
宽依赖:1个父RDD的分区可对应一个子RDD的多个分区。即父子RDD间是一对多关系。一个父RDD分区影响多个子RDD分区数据,所以宽依赖相邻RDD不能属于一个Stage。宽依赖会存在shuffle.
map、filter、union是窄依赖,groupByKey、sortByKey会用到Shuffle,所以是宽依赖。
join分两种情况:
(1)对输入进行协同划分的join是窄依赖。协同划分指多个父RDD的某一分区的所有key只落在子RDD的同一个分区,即父子RDD分区是多对一关系,是窄依赖;
(2)对输入进行非协同划分的join是宽依赖。非协同划分指多个父RDD的某一分区的key可能落在子RDD的不同分区,即父子RDD分区是一对多关系,是宽依赖
5、Stage的划分
Spark通过分析各个RDD的依赖关系生成了DAG,再通过分析各个RDD中的分区之间的依赖关系来决定如何划分Stage,具体划分方法是:
(1)在DAG中进行反向解析,遇到宽依赖就断开(断开指宽依赖的两个RDD分属于两个Stage)
(2)遇到窄依赖就把当前的RDD加入到Stage中(既相邻窄依赖RDD属于一个Stage)
(3)将窄依赖尽量划分在同一个Stage中,可以实现流水线计算(RDD中一个分区转换操作完成即可计入下一个处理,不用等到本RDD其他分区处理完成,提高效率。见下面流水线操作举例)
结论:每一个Stage内的RDD分区间都是窄依赖,多个Stage的RDD分区间是宽依赖(存在Shuffle)。一个RDD只属于一个Stage。
C、D、F间是窄依赖,A和B、F和G是宽依赖,B和G又是窄依赖

流水线操作实例:分区7通过map操作生成的分区9,可以不用等待分区8到分区10这个map操作的计算结束,而是继续进行union操作,得到分区13,这样流水线执行大大提高了计算的效率
Stage的类型包括两种:ShuffleMapStage和ResultStage,具体如下:
(1)ShuffleMapStage:不是最终的Stage,在它之后还有其他Stage,所以,它的输出一定需要经过Shuffle过程,并作为后续Stage的输入;这种Stage是以Shuffle为输出边界(即输出的是Shuffle结果),其输入边界可以是从外部获取数据,也可以是另一个ShuffleMapStage的输出,其输出可以是另一个Stage的开始;在一个Job里可能有该类型的Stage,也可能没有该类型Stage;
(2)ResultStage:最终的Stage,没有输出,而是直接产生结果或存储。这种Stage是直接输出结果,其输入边界可以是从外部获取数据,也可以是另一个ShuffleMapStage的输出。在一个Job里必定有该类型Stage。
因此,一个Job含有一个或多个Stage,其中至少含有一个ResultStage。
6、RDD运行过程
通过上述对RDD概念、依赖关系和Stage划分的介绍,结合之前介绍的Spark运行基本流程,再总结一下RDD在Spark架构中的运行过程:
(1)从HDFS读取数据创建RDD对象;
(2)SparkContext负责计算RDD之间的依赖关系,构建DAG;
(3)DAGScheduler负责把DAG图分解成多个Stage,每个Stage中包含了多个Task,每个Task会被TaskScheduler分发给各个WorkerNode上的Executor去执行。

浙公网安备 33010602011771号