文章目录

  • 一 运行架构
  • 1 运行架构
  • 2 核心组件
  • (1) Driver
  • (2) Executor
  • (3) Master & Worker
  • (4) ApplicationMaster
  • 3 核心概念
  • (1) Executor与Core(核)
  • (2) 并行度(Parallelism)
  • (3) 有向无环图(DAG)
  • 4 提交流程
  • (1) Yarn Client模式
  • (2) Yarn Cluster模式
  • 二 RDD
  • 1 什么是RDD
  • 2 装饰者设计模式
  • 3 RDD功能的组合
  • 4 基础概念
  • 5 核心配置属性
  • 6 执行原理
  • 7 RDD创建
  • (1)从集合(内存)中创建RDD
  • (2)从外部存储(文件)创建RDD
  • (3)从其他RDD创建
  • (4)直接创建RDD


一 运行架构

1 运行架构

Spark框架的核心是一个计算引擎,整体来说,它采用了标准 master-slave 的结构。

如下图所示,它展示了一个 Spark执行时的基本结构。图形中的Driver表示master,负责管理整个集群中的作业任务调度。图形中的Executor 则是 slave,负责实际执行任务。

简述Spark框架的主要特点 spark架构详解_数据

2 核心组件

由上图可以看出,对于Spark框架有两个核心组件:

(1) Driver

Spark驱动器节点,用于执行Spark任务中的main方法,负责实际代码的执行工作。Driver在Spark作业执行时主要负责:

  • 将用户程序转化为作业(job)
  • 在Executor之间调度任务(task)
  • 跟踪Executor的执行情况
  • 通过UI展示查询运行情况

实际上,无法准确地描述Driver的定义,因为在整个的编程过程中没有看到任何有关Driver的字眼。所以简单理解,所谓的Driver就是驱使整个应用运行起来的程序,也称之为Driver类。

(2) Executor

Spark Executor是集群中工作节点(Worker)中的一个JVM进程,负责在 Spark 作业中运行具体任务(Task),任务彼此之间相互独立。Spark 应用启动时,Executor节点被同时启动,并且始终伴随着整个 Spark 应用的生命周期而存在。如果有Executor节点发生了故障或崩溃,Spark 应用也可以继续执行,会将出错节点上的任务调度到其他Executor节点上继续运行。

Executor有两个核心功能:

  • 负责运行组成Spark应用的任务,并将结果返回给驱动器进程
  • 它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内存式存储。RDD 是直接缓存在Executor进程内的,因此任务可以在运行时充分利用缓存数据加速运算。

(3) Master & Worker

Spark集群的独立部署环境中,不需要依赖其他的资源调度框架,自身就实现了资源调度的功能,所以环境中还有其他两个核心组件:Master和Worker,这里的Master是一个进程,主要负责资源的调度和分配,并进行集群的监控等职责,类似于Yarn环境中的RM,,而Worker也是进程,一个Worker运行在集群中的一台服务器上,由Master分配资源对数据进行并行的处理和计算,类似于Yarn环境中NM。

(4) ApplicationMaster

Hadoop用户向YARN集群提交应用程序时,提交程序中应该包含ApplicationMaster,用于向资源调度器申请执行任务的资源容器Container,运行用户自己的程序任务job,监控整个任务的执行,跟踪整个任务的状态,处理任务失败等异常情况。

说的简单点就是,ResourceManager(资源)和Driver(计算)之间的解耦合靠的就是ApplicationMaster。

3 核心概念

(1) Executor与Core(核)

Spark Executor是集群中运行在工作节点(Worker)中的一个JVM进程,是整个集群中的专门用于计算的节点。在提交应用中,可以提供参数指定计算节点的个数,以及对应的资源。这里的资源一般指的是工作节点Executor的内存大小和使用的虚拟CPU核(Core)数量。

应用程序相关启动参数如下:

名称

说明

–num-executors

配置Executor的数量

–executor-memory

配置每个Executor的内存大小

–executor-cores

配置每个Executor的虚拟CPU core数量

(2) 并行度(Parallelism)

在分布式计算框架中一般都是多个任务同时执行,由于任务分布在不同的计算节点进行计算,所以能够真正地实现多任务并行执行,这里是并行,而不是并发。这里将整个集群并行执行任务的数量称之为并行度。一个作业到底并行度是多少取决于框架的默认配置。应用程序也可以在运行过程中动态修改。

(3) 有向无环图(DAG)

简述Spark框架的主要特点 spark架构详解_架构_02

大数据计算引擎框架根据使用方式的不同一般会分为四类,其中第一类就是Hadoop所承载的MapReduce,它将计算分为两个阶段,分别为 Map阶段 和 Reduce阶段。对于上层应用来说,就不得不想方设法去拆分算法,甚至于不得不在上层应用实现多个 Job 的串联,以完成一个完整的算法,例如迭代计算。

由于这样的弊端,催生了支持 DAG 框架的产生。因此,支持 DAG 的框架被划分为第二代计算引擎。如 Tez 以及更上层的 Oozie。这里不去细究各种 DAG 实现之间的区别,不过对于当时的 Tez 和 Oozie 来说,大多还是批处理的任务。接下来就是以 Spark 为代表的第三代的计算引擎。第三代计算引擎的特点主要是 Job 内部的 DAG 支持(不跨越 Job),以及实时计算。

这里所谓的有向无环图,并不是真正意义的图形,而是由Spark程序直接映射成的数据流的高级抽象模型。简单理解就是将整个程序计算的执行过程用图形表示出来,这样更直观,更便于理解,可以用于表示程序的拓扑结构。

DAG(Directed Acyclic Graph)有向无环图是由点和线组成的拓扑图形,该图形具有方向,不会闭环。

4 提交流程

所谓的提交流程,其实就是开发人员根据需求写的应用程序通过Spark客户端提交给Spark运行环境执行计算的流程。在不同的部署环境中,这个提交过程基本相同,但是又有细微的区别,这里不进行详细的比较,但是因为国内工作中,将Spark引用部署到Yarn环境中会更多一些。

简述Spark框架的主要特点 spark架构详解_Spark DAG构建过程_03

Spark应用程序提交到Yarn环境中执行的时候,一般会有两种部署执行的方式:Client和Cluster。两种模式主要区别在于:Driver程序的运行节点位置。

(1) Yarn Client模式

Client模式将用于监控和调度的Driver模块在客户端执行,而不是在Yarn中,所以一般用于测试。

  • Driver在任务提交的本地机器上运行
  • Driver启动后会和ResourceManager通讯申请启动ApplicationMaster
  • ResourceManager分配container,在合适的NodeManager上启动ApplicationMaster,负责向ResourceManager申请Executor内存
  • ResourceManager接到ApplicationMaster的资源申请后会分配container,然后ApplicationMaster在资源分配指定的NodeManager上启动Executor进程
  • Executor进程启动后会向Driver反向注册,Executor全部注册完成后Driver开始执行main函数
  • 之后执行到Action算子时,触发一个Job,并根据宽依赖开始划分stage,每个stage生成对应的TaskSet,之后将task分发到各个Executor上执行。

(2) Yarn Cluster模式

Cluster模式将用于监控和调度的Driver模块启动在Yarn集群资源中执行。一般应用于实际生产环境。

  • 在YARN Cluster模式下,任务提交后会和ResourceManager通讯申请启动ApplicationMaster,
  • 随后ResourceManager分配container,在合适的NodeManager上启动ApplicationMaster,此时的ApplicationMaster就是Driver。
  • Driver启动后向ResourceManager申请Executor内存,ResourceManager接到ApplicationMaster的资源申请后会分配container,然后在合适的NodeManager上启动Executor进程
  • Executor进程启动后会向Driver反向注册,Executor全部注册完成后Driver开始执行main函数,
  • 之后执行到Action算子时,触发一个Job,并根据宽依赖开始划分stage,每个stage生成对应的TaskSet,之后将task分发到各个Executor上执行。

二 RDD

Spark计算框架为了能够进行高并发和高吞吐的数据处理,封装了三大数据结构,用于处理不同的应用场景。三大数据结构分别是:

  • RDD : 弹性分布式数据集
  • 累加器:分布式共享只写变量
  • 广播变量:分布式共享只读变量

接下来一起看看这三大数据结构是如何在数据处理中使用的。

数据结构:组织和管理数据的方式,如数组,链表,HashMap并不是一种数据结构,它是将多种数据结构组合在一起形成的一个容器对象

1 什么是RDD

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据处理模型。代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。

RDD计算模型更适合并行计算和重复使用。

实现并行计算条件:在内部将数据分开(分区),将各个分区的数据发送给多个节点(Executor)进行计算

实现重复使用条件:计算单一,功能简单,从java角度理解就是能抽取出通用方法

RDD中有适合并行计算的分区操作,并封装了最小的计算单元,目的是更适合重复使用

Spark的计算主要就是通过组合RDD的操作,完成业务需求

2 装饰者设计模式

将多个功能组合在一起完成最终的操作,称为装饰者设计模式,用于扩展功能

如:IO 输入输出

字节流,字符流

InputStream in = new FileInputStream("xxxxx");
int i = -1;
while( (i = in.read()) != -1){
    sout(i);
}
in.close;

一个一个字节取数据,太慢了,为增加效率,增加一个缓冲区

InputStream in = new BufferedInputStream(
	new FileInputStream("xxxxx")
);
int i = -1;
while( (i = in.read(1024)) != -1){
    sout(i);
}
in.close;

这时效率增加,但是读取文件的功能并不是由BufferedInputStream提供,它只是为程序增加了一个缓冲区(Buffer),读取文件功能仍然由FileInputStream完成,将多个功能组合在一起完成最终的需求,这就是装饰者设计模式。

此时想要一行一行读取数据

需要注意Stream为字节流,Reader为字符流,两者不能直接进行转换,所以需要添加一个转换流InputStreamReader,“UTF-8”为两者的转换方式

Reader in = new BufferedReader(
	new InputStreamReader(
		new FileInputStream("xxxxx"),
		"UTF-8"
	)
);
String s = null;
while( (s = in.readLine()) != null){
    sout(s);
}
in.close;

底层还是FileInputStream一个一个字节读取数据,这时多个字节通过InputStreamReader转换会生成一个字符,如“A,B,C”三个字节转换成一个字符“中”

这时就完成了功能的叠加

  • FileInputStream:负责读文件
  • InputStreamReader:将字节转换成字符
  • BufferedReader:增加缓冲区,提升效率

3 RDD功能的组合

RDD的功能扩展和IO一样,也采用了装饰者设计模式,将不同的功能组合在一起完成业务需求

RDD中的collect方法类似于IO中的read方法,read方法不执行,上面的三个new不会执行,同样,collect方法不执行,collect方法以上代码也不会执行,只是告诉了程序如何组合上述功能

RDD不存储任何数据,只封装逻辑,每一层只做自己的事情

sc.textFile("data/word.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect

4 基础概念

  • 弹性
  • 存储的弹性:内存与磁盘的自动切换;
  • 容错的弹性:数据丢失可以自动恢复;
  • 计算的弹性:计算出错重试机制;
  • 分片的弹性:可根据需要重新分片。
  • 分布式:数据存储在大数据集群不同节点上
  • 数据集:RDD封装了计算逻辑,并不保存数据,只封装逻辑

RDD[String] 并不是RDD集合中存储String类型数据,而是RDD对String类型数据进行计算,是数据的处理类型而不是数据的存储类型

  • 数据抽象:RDD是一个抽象类,需要子类具体实现
  • 不可变:RDD封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD里面封装计算逻辑

类比于不可变集合

  • 可分区、并行计算

5 核心配置属性

简述Spark框架的主要特点 spark架构详解_spark_04

  • 分区列表

RDD数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。

简述Spark框架的主要特点 spark架构详解_spark_05

  • 分区计算函数

Spark在计算时,是使用分区函数对每一个分区进行计算

简述Spark框架的主要特点 spark架构详解_数据_06

  • RDD之间的依赖关系

RDD是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个RDD建立依赖关系

简述Spark框架的主要特点 spark架构详解_大数据_07

  • 分区器(可选)

当数据为KV类型数据时,可以通过设定分区器自定义数据的分区

简述Spark框架的主要特点 spark架构详解_数据_08

  • 首选位置(可选)

计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算

数据和计算的位置称为本地化

数据和计算的位置在同一个进程上称为进程本地化

数据和计算的位置在同一个节点上称为节点本地化

数据和计算的位置在同一个机架上称为机架本地化

移动数据不如移动计算

简述Spark框架的主要特点 spark架构详解_数据_09

6 执行原理

从计算的角度来讲,数据处理过程中需要计算资源(内存 & CPU)和计算模型(逻辑)。执行时,需要将计算资源和计算模型进行协调和整合。

Spark框架在执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个的计算任务。然后将任务发到已经分配资源的计算节点上, 按照指定的计算模型进行数据计算,最后得到计算结果。

RDD是Spark框架中用于数据处理的核心模型,接下来我们看看,在Yarn环境中,RDD的工作原理:

  • 启动Yarn集群环境

简述Spark框架的主要特点 spark架构详解_spark_10

  • Spark通过申请资源创建调度节点和计算节点

简述Spark框架的主要特点 spark架构详解_spark_11

  • Spark框架根据需求将计算逻辑根据分区划分成不同的任务

简述Spark框架的主要特点 spark架构详解_架构_12

  • 调度节点将任务根据计算节点状态发送到对应的计算节点进行计算

简述Spark框架的主要特点 spark架构详解_数据_13

从以上流程可以看出RDD在整个流程中主要用于将逻辑进行封装,并生成Task发送给Executor节点执行计,接下来看看Spark框架中RDD是具体是如何进行数据处理的。

7 RDD创建

在Spark中创建RDD的创建方式可以分为四种:

(1)从集合(内存)中创建RDD

从集合中创建RDD,Spark主要提供了两个方法:parallelize和makeRDD

parallelize用于构建RDD,也可以将这个集合当成数据模型处理的数据源

parallelize单词表示并行

val sparkConf =
    new SparkConf().setMaster("local[*]").setAppName("spark")
val sparkContext = new SparkContext(sparkConf)

val rdd1 = sparkContext.parallelize(
    List(1,2,3,4)
)
val rdd2 = sparkContext.makeRDD(
    List(1,2,3,4)
)
rdd1.collect().foreach(println)
rdd2.collect().foreach(println)
sparkContext.stop()

从底层代码实现来讲,makeRDD方法其实就是parallelize方法,makeRDD方法定义如下

def makeRDD[T: ClassTag](
    seq: Seq[T],
    numSlices: Int = defaultParallelism): RDD[T] = withScope {
  parallelize(seq, numSlices)
}

(2)从外部存储(文件)创建RDD

由外部存储系统的数据集创建RDD包括:本地的文件系统,所有Hadoop支持的数据集,比如HDFS、HBase等。

val sparkConf =
    new SparkConf().setMaster("local[*]").setAppName("spark")
val sparkContext = new SparkContext(sparkConf)

val fileRDD: RDD[String] = sparkContext.textFile("data/word.txt")
fileRDD.collect().foreach(println)
sparkContext.stop()

textFile方法可以通过路径获取数据,所以可以将文件作为数据处理的数据源

textFile路径可以是相对路径,也可以是绝对路径,甚至可以是分布式存储HDFS路径

textFile路径不仅仅可以是文件路径,也可以是目录路径,如果是目录路径,会将路径内的所有文件全部读取过来

textFile路径也可以是通配符路径

val fileRDD: RDD[String] = sparkContext.textFile("data/word*.txt")

如果在读取文件后,想要获取文件数据的来源,使用wholeTextFiles方法,第一个值为文件路径,第二个值为文件内容

val rdd: RDD[(String, String)] = sparkContext.wholeTextFiles("data/world*.txt")

(3)从其他RDD创建

主要是通过一个RDD运算完后,再产生新的RDD。

(4)直接创建RDD

使用new的方式直接构造RDD,一般由Spark框架自身使用。