“来肝” spark-core

405 阅读19分钟

Spark Core

术语

Spark Core是整个Spark项目的核心,它包括了Spark的基本功能和API,包括RDD、任务调度和执行、部署和资源管理等。 Spark Core中还有很多其他的组件,比如RDD、算子、依赖等,它们是构成Spark计算框架的重要部分。下面是一些术语的解释:

  • RDD:Resilient Distributed Dataset,是Spark中的一个核心概念,是一个不可变的、可分区的、容错的数据集合;
  • 算子:是对RDD进行操作的方法,分为两种:转化算子和动作算子;
  • 依赖:是指RDD之间的关系,分为窄依赖和宽依赖;
  • 窄依赖:指子RDD中的每个分区最多只依赖于父RDD中的一个分区;
  • 宽依赖:指子RDD中的每个分区依赖于父RDD中多个分区。

Spark Core的架构包括资源层和计算层,资源层由Master和Worker组成,计算层包括SparkContext、DAGScheduler、TaskScheduler、Executor等组件。Spark应用程序的执行流程如下:

  1. 用户创建SparkContext对象,初始化各个组件;
  2. 用户定义RDD,并进行转化操作得到新的RDD;
  3. 用户调用Action算子触发任务调度,生成相应的DAG;
  4. DAGScheduler根据DAG生成相应的TaskSet,TaskScheduler负责将TaskSet中的Task分发给Executor执行;
  5. Executor将Task执行的结果返回给Driver。

架构

Spark Core的架构主要分为两层,资源层和计算层。资源层包括Master和Worker两个角色,Master接受Worker的注册和资源整理,接受计算层的资源申请;Worker是启动计算层的角色,向Master汇报资源使用。计算层包括Client、Driver和Executor三个角色,Client是与用户交互的接口,Driver是处理用户代码的主要组件,负责任务调度和执行,Executor是处理具体任务的组件,负责执行用户代码。

应用

Spark Core的编程模型主要是基于RDD(Resilient Distributed Dataset)的,它是Spark中最基本的数据结构。除此之外,Spark Core还提供了一系列的算子(Operator)来对RDD进行各种操作。

编程模型

RDD

Resilient Distributed Dataset

RDD是Spark中最基本的数据结构,它代表一个被分区的只读数据集合。RDD具有以下属性:

  • 一组分区(Partition);
  • 一个用于计算每个分区的函数;
  • 一组依赖其他RDD的依赖关系。

RDD的依赖关系分为两种:窄依赖(Narrow Dependency)和宽依赖(Wide Dependency)。窄依赖指的是每个父RDD的分区最多只有一个子RDD的分区依赖,而宽依赖指的是每个父RDD的分区可以被多个子RDD的分区依赖。

HadoopRDD

HadoopRDD是Spark中最基本的RDD之一,它将Hadoop中的文件作为输入,可以通过读取Hadoop中的数据来创建RDD。

MapPartitionsRDD

MapPartitionsRDD是基于RDD的一种转换操作,它对每个分区中的所有元素应用指定的函数,生成新的RDD。相对于普通的map函数,它只将函数应用到每个分区而不是每个元素,可以减少函数调用的开销,提高性能。

算子

算子是Spark中用于对RDD进行操作的函数,分为三类:创建算子、转换算子和行动算子。

创建算子

创建算子用于创建新的RDD。

  • sc.textfile:从文件中读取数据创建一个新的RDD。
转换算子

转换算子用于将一个RDD转换为另一个RDD。

  • map、flatMap、filter:对RDD中的每个元素应用指定的函数,生成新的RDD。
  • reduceByKey/combineByKey、sortbykey:按键对RDD进行聚合或排序。
行动算子

行动算子用于对RDD执行计算操作并返回结果。

  • foreach、collect、take、count:对RDD中的元素执行指定的操作。

依赖关系

依赖关系指的是RDD之间的依赖关系,分为窄依赖和宽依赖。

窄依赖

窄依赖指的是每个父RDD的分区最多只有一个子RDD的分区依赖。

宽依赖

宽依赖指的是每个父RDD的分区可以被多个子RDD的分区依赖。宽依赖需要进行数据的Shuffle操作,开销较大,应该尽量避免使用。

Shuffle

Shuffle是Spark中用于数据重分区的操作,它需要将数据从一个节点传输到另一个节点,开销较大。

Shuffle的处理

Shuffle的处理分为以下步骤:

  • SortShuffleManager:对Shuffle数据进行排序。
  • ShuffleWriter:将Shuffle数据写入磁盘。
  • ShuffleReader:从磁盘读取Shuffle数据。

面向RDD

面向RDD是Spark中的一种编程模式,它将RDD作为基本数据结构,将操作和计算任务封装在RDD中,实现了数据和计算的解耦。

Iterator是模板方法

在面向RDD的编程模式中,Iterator是模板方法,它包括以下步骤:

  • persist中去查找有没有存过数据;
  • checkpoint:从HDFS中取数据;
  • compute:计算数据。
是一个单向链表

RDD是一个单向链表,每个RDD包含了它的血统(Lineage)和Pipeline(流水线)。

血统

血统是指RDD之间的依赖关系,它是一个递归的关系,每个RDD都有一个或多个父RDD,直到最初的RDD为止。

Pipeline

Pipeline是指对RDD进行操作的一系列步骤,包括转换和行动算子。Pipeline是一个迭代器的嵌套迭代引用,表示在一个Task中对RDD进行操作。

源码

Spark Core的源码基于Standalone模式实现。其中,资源层包括Master和Worker两个角色,计算层包括Client、Driver和Executor三个角色。

基于Standalone

资源层

资源层包括Master和Worker两个角色。Master接受Worker的注册和资源整理,接受计算层的资源申请;Worker是启动计算层的角色,向Master汇报资源使用。

角色
Master

Master是资源层的核心组件,主要职责包括接受Worker的注册、资源的整理,接受计算层的资源申请等。

Worker

Worker是启动计算层的角色,向Master汇报资源使用,当Master分配任务时,Worker会启动Executor执行具体的任务。

RPC

在资源层中,使用RPC(Remote Procedure Call)来实现Master和Worker之间的通信。

Endpoint

Endpoint是RPC的核心组件,它是一个接口,定义了发送和接收消息的方法,包括ref、send、ask、receive和receiveAndReplay等。

Dispatcher

Dispatcher是一个继承自Endpoint的类,它负责接收和分发消息。

Netty

Netty是一个异步事件驱动的网络应用框架,它可以快速开发高性能、高可靠性的网络应用程序。在Spark中,Netty被用作RPC的网络传输层。

计算层

计算层包括Client、Driver和Executor三个角色。Client是与用户交互的接口,Driver是处理用户代码的主要组件,负责任务调度和执行,Executor是处理具体任务的组件,负责执行用户代码。

Client、Cluster

Client是与用户交互的接口,Cluster是处理Spark集群中的任务的组件。

Driver在哪里

Driver是处理用户代码的主要组件,它负责任务调度和执行。在Spark中,Driver可以运行在Cluster或者Client上。

Cluster

Cluster是处理Spark集群中的任务的组件,它包括了Client、Driver和Executor三个角色。

  • Client:通过资源层申请Driver;
  • Driver:处理Spark任务的组件,它包括了SparkContext和Backend;
  • SparkContext:是Spark应用程序的入口点,负责初始化Spark应用程序中的各个组件,如DAGScheduler、TaskScheduler等;
  • Backend:负责与资源层进行通信,向Master注册并请求资源,然后启动Executor。

Executor是在Worker节点上运行的进程,负责执行Spark任务中的各个Task,其中包括数据读取、数据处理和结果输出等。Spark任务的执行流程如下:

  1. RDD的Action算子触发任务调度,调用SparkContext的runJob方法;
  2. DAGScheduler将最后一个RDD作为参数,生成相应的Stage;
  3. TaskScheduler根据Stage中的分区数生成相应的Task,并将其加入到TaskSet中;
  4. Worker节点上的Executor根据TaskSet中的Task进行计算,并将结果返回给Driver。

在Spark集群中:Driver和Executor是两个关键的角色。Driver是负责任务调度和协调的控制节点,而Executor则是负责具体的任务执行。具体来说,Driver主要包含以下组件:

1. driverEndpoint

DriverEndpoint是Driver的核心组件,负责与Executor进行通信,包括任务调度、任务状态的更新等。

2. appClient

appClient是负责将Driver注册到资源管理器(如YARN或Mesos)中,并触发资源调度的组件。具体过程如下:

2.1. 去资源层的master注册

Driver首先会向资源管理器发送注册请求,请求注册为一个应用程序。

2.1.1. 在注册过程中,会产生以下两个关键组件:
2.1.1.1. driverEndpoint

注册完成后,Driver会创建一个DriverEndpoint组件,并将其注册到资源管理器中。

2.1.1.2. appClient

Driver还会创建一个appClient组件,并将其注册到资源管理器中。appClient会负责向资源管理器请求资源,并触发资源调度。

2.1.1.2.1. 在master中触发资源调度

appClient会向资源管理器发送资源请求,请求分配一定数量的Executor。资源管理器会根据当前集群的负载情况,决定是否分配资源给Driver,并通知Driver资源已分配完成。

2.1.1.2.1.1. 产生executor

在资源分配完成后,Executor会被创建并注册到DriverEndpoint中。此时,DriverEndpoint已经可以与Executor进行通信,并向其分配任务。

3. ExecutorBackEnd

ExecutorBackEnd是Executor的核心组件,负责与Driver进行通信,并接收任务调度和状态更新等指令。具体过程如下:

3.1. 向Driver反向注册

Executor在启动时,会向DriverEndpoint发送注册请求,请求注册为一个Executor。

3.2. Executor

Executor是负责具体任务执行的组件,它包含一个线程池,用于执行任务。具体来说,Executor的执行过程如 下:

3.2.1. threadPool

Executor中包含一个线程池,用于执行任务。

3.2.1.1. task

线程池中的每个线程用于执行一个任务,任务由DriverEndpoint分配。

任务调度和执行

在Spark中,任务调度是由DAGScheduler组件完成的,它负责将任务按照依赖关系划分为不同的stage,并为每个stage生成对应的task。具体过程如下:

1. rdd的action算子

当调用RDD的action算子时,会触发任务调度过程。

1.1. sc.runJob

sc.runJob是DAGScheduler的核心方法,它会根据rdd的依赖关系,将整个任务划分为不同的stage,并为每个stage生成对应的task。

2. DAGScheduler

DAGScheduler是任务调度的核心组件,它会将任务按照依赖关系划分为不同的stage,并为每个stage生成对应的task。具体过程如下:

2.1. 是把job的最后一个rdd作为参数

DAGScheduler会将整个任务按照依赖关系划分为不同的stage,每个stage都由一个或多个RDD组成。具体来说,DAGScheduler会以job的最后一个RDD作为参数,对整个任务进行划分。

2.2. stage

每个stage都由一个或多个RDD组成,其中最后一个RDD代表着当前stage。

2.2.1. 最后一个rdd代表最后一个stage

每个stage都由一个或多个RDD组成,其中最后一个RDD代表着当前stage。

2.2.1.1. stage中只有一个rdd

当一个stage中只有一个RDD时,该stage即为最后一个stage。

2.3. 递归+遍历

DAGScheduler会通过递归和遍历的方式,将整个任务按照依赖关系划分为不同的stage。

2.3.1. 递归

DAGScheduler会通过递归的方式,将整个任务划分为不同的stage。具体来说,DAGScheduler会以stage换言之以shuffleDep为边界,进行递归划分。

2.3.1.1. 以stage换言之以shuffleDep为边界

DAGScheduler会以stage换言之以shuffleDep为边界,进行递归划分。shuffleDep是指一个RDD与其他RDD之间的依赖关系,其中如果存在shuffle操作,就需要将数据进行重新分区,这就需要进行网络传输和磁盘读写,会大大降低性能。

2.3.2. 遍历

在递归的过程中,DAGScheduler会通过遍历的方式,寻找shuffleDep的边界。具体来说,DAGScheduler会从最后一个RDD开始,向前遍历,直到找到一个RDD与其他RDD之间存在shuffleDep的边界为止。

2.3.2.1. 寻找shuffleDep的过程是触发的遍历

在递归的过程中,DAGScheduler会通过遍历的方式,寻找shuffleDep的边界。具体来说,DAGScheduler会从最后一个RDD开始,向前遍历,直到找到一个RDD与其他RDD之间存在shuffleDep的边界为止。

2.4. 回归过程中触发task调度提交

在递归的过程中,DAGScheduler会为每个stage生成对应的task,并将其填充到TaskSet中。具体来说,DAGScheduler会在回归的过程中,触发task调度提交,并将生成的task填充到TaskSet中。

2.5. stage

对于每个stage,DAGScheduler会产生一个taskbinary,并将其广播出去。然后,根据分区数,为每个stage产生对应的task,并将其填充到TaskSet中。

2.5.1. task的数量是最后一个RDD的分区的数量决定的

对于每个stage,任务的数量是最后一个RDD的分区数量决定的。

2.5.2. 最佳计算位置

DAGScheduler会根据数据本地性和负载均衡等原则,选择最佳的计算位置。

2.5.3. stage会产生一个taskbinary,并广播出去

对于每个stage,DAGScheduler会产生一个taskbinary,并将其广播出去。taskbinary包含了任务执行所需的所有信息,如代码、依赖关系等。

2.5.4. 一个stage根据分区数,产生对应的task

对于每个stage,DAGScheduler会根据分区数,产生对应的task,并将其填充到TaskSet中。

2.5.5. 最终将tasks填充到taskset

当DAGScheduler为每个stage产生对应的task后,将其填充到TaskSet中,并触发TaskScheduler进行调度执行。

3. TaskScheduler

TaskScheduler是任务调度的核心组件,它负责将任务分配给Executor,并监控任务的执行状态。具体过程如下:

3.1. schdduleMode

TaskScheduler有两种调度模式:FIFO和Pair。

3.1.1. FIFO

在FIFO模式下,TaskScheduler会按照任务提交的顺序,依次将任务分配给Executor。

3.1.2. Pair

在Pair模式下,TaskScheduler会根据数据本地性和负载均衡等原则,选择最佳的计算位置,并将任务分配给相应的Executor。

3.2. TaskSetManager

TaskSetManager是TaskScheduler的核心组件,它负责管理TaskSet中的任务,并将其分配给Executor执行。具体来说,TaskSetManager会根据调度模式,从TaskSet中选择一批任务,并将其分配给相应的Executor执行。

4. Executor

在Executor中,任务的具体执行过程包含三个阶段:输入、计算和输出。具体来说,Executor中的执行过程如下:

4.1. runtask

runtask是Executor的核心方法,它会将一个任务分为三个阶段:输入、计算和输出,并执行这些阶段。

4.1.1. SparkEnv

SparkEnv是Executor的核心组件,它包含了内存管理、存储管理、网络通信等功能。

4.1.1.1. memoryManager

memoryManager是SparkEnv的一个子组件,它负责管理任务执行过程中的内存使用情况。

4.1.1.1.1. executionMemory

executionMemory是memoryManager的一个子组件,它用于管理任务执行过程中的内存使用情况。

4.1.1.1.2. memoryStore

memoryStore是memoryManager的一个子组件,它用于管理任务执行过程中的内存存储。

4.1.1.2. blockManager

blockManager是SparkEnv的一个子组件,它负责管理数据的存储和传输。

4.1.1.2.1. mem

mem是blockManager的一个子组件,它用于管理数据在内存中的存储。

4.1.1.2.2. disk

disk是blockManager的一个子组件,它用于管理数据在磁盘中的存储。

4.1.1.3. mapoutputracker

mapoutputracker是SparkEnv的一个子组件,它用于跟踪Map任务输出的位置信息。

4.1.1.4. nettytransformserver

nettytransformserver是SparkEnv的一个子组件,它用于处理网络传输请求。

4.1.1.5. sortShuffleManager

sortShuffleManager是SparkEnv的一个子组件,它用于管理Shuffle操作的内存和磁盘存储。

4.1.2. task

一个任务在Executor中会被分为三个阶段:输入、计算和输出。

4.1.2.1. 1,输入

在输入阶段,任务会从HadoopRDD、持久化RDD、检查点RDD、Shuffle Reader等数据源中读取数据。

4.1.2.1.1. hadooprdd

HadoopRDD是一个基于Hadoop InputFormat的RDD,它用于读取Hadoop中的数据。

4.1.2.1.2. persist

持久化RDD是已经被计算过的RDD,它可以被缓存到内存或磁盘中,以避免重复计算。

4.1.2.1.3. checkpoint

检查点RDD是已经被计算过的RDD,它会被保存到磁盘中,以便于后续的故障恢复。

4.1.2.1.4. shuffle-reader

Shuffle Reader是用于读取Shuffle数据的组件。

4.1.2.2. 2,计算

在计算阶段,任务会对输入数据进行计算,并生成输出数据。计算过程中,Spark会采用Pipeline和Iter的方式,以提高计算效率。

4.1.2.2.1. pipeline:iter

Pipeline是一种将多个操作串联起来的方式,以减少数据的中间存储和网络传输。而Iter是一种将多个操作合并在一起的方式,以减少计算的开销。

4.1.2.3. 3,输出

在输出阶段,任务会将计算结果输出到Shuffle Writer或结果中。

4.1.2.3.1. shuffle-writer

Shuffle Writer是用于写入Shuffle数据的组件。

4.1.2.3.2. result

结果是任务执行的最终结果,它可以被返回给Driver或写入到磁盘中。

4.1.3. sortShuffleManager

sortShuffleManager是用于管理Shuffle操作的内存和磁盘存储的组件。它包含了registerHandle、getWriter、getReader等方法。

4.1.3.1. registerHandle

registerHandle用于注册Shuffle数据的处理器。

4.1.3.2. getWriter

getWriter用于获取Shuffle数据的写入器。

4.1.3.2.1. bypass

bypass是getWriter的一种实现方式,它用于处理数据本地性好的情况。

4.1.3.2.2. base

base是getWriter的一种实现方式,它用于处理数据本地性差的情况。

4.1.3.2.2.1. map

map是base的一种实现方式,它用于将数据从Map任务写入到磁盘中。

4.1.3.2.2.2. buffer

buffer是base的一种实现方式,它用于将数据从缓冲区写入到磁盘中。

4.1.3.2.3. unsafe

unsafe是getWriter的一种实现方式,它用于使用Unsafe API写入Shuffle数据。

4.1.3.3. getReader

getReader用于获取Shuffle数据的读取器,它包括了dep、iter等参数。

4.1.3.3.1. dep

dep是Shuffle数据的依赖关系。

4.1.3.3.1.1. iter

iter是dep的一种实现方式,它用于遍历Shuffle数据的依赖关系。

4.1.4. BlockManager

BlockManager是用于管理数据块的组件,它包括了shuffle-writer、persist、broadcast等方法。

4.1.4.1. shuffle-writer

shuffle-writer是用于写入Shuffle数据的组件,它可以将数据写入到内存或磁盘中。

4.1.4.1.1. 结果

写入Shuffle数据的结果可以被返回给Driver或写入到磁盘中。

4.1.4.2. persist

persist是用于将RDD缓存到内存或磁盘中的方法,它可以使用StorageLevel指定缓存级别。

4.1.4.2.1. StorageLevel

StorageLevel是用于指定缓存级别的枚举类型,它包括了MemoryOnly、MemoryAndDisk、Ser等级别。

4.1.4.2.1.1. MemoryOnly

MemoryOnly是一种只缓存到内存中的级别。

4.1.4.2.1.2. MemoryAndDisk

MemoryAndDisk是一种缓存到内存和磁盘中的级别。

4.1.4.2.1.3. ser

ser是一种序列化级别,它可以将数据序列化后存储到内存或磁盘中。

4.1.4.3. broadcast

broadcast是用于广播变量的方法,它可以将变量缓存到内存中,以便于多个任务之间共享。

4.1.5. taskmemoryManager

taskmemoryManager是用于管理任务内存的组件,它可以为每个任务分配独立的内存空间。

4.1.5.1. 每task一个

每个任务都有自己独立的内存空间,以避免任务之间的干扰。

4.1.5.2. 计算过程中

在任务的计算过程中,会使用Shuffle Writer等组件向内存中写入数据,并使用Unsafe API等技术来提高写入速度。

4.1.5.2.1. shuffle-writer

Shuffle Writer是一种用于写入Shuffle数据的组件,它可以将数据写入到内存或磁盘中。

4.1.5.2.1.1. unsafe

Unsafe是一种用于提高写入速度的API,它可以绕过Java虚拟机的内存管理机制,直接访问内存。而Shuffle Writer则可以使用Unsafe API来提高写入速度。

4.1.5.2.1.2. base

如果内存不足以容纳所有的Shuffle数据,Shuffle Writer会将数据写入到磁盘中,以避免OOM异常的发生。而在从磁盘中读取数据时,则会使用base等组件来提高读取速度。

4.1.5.2.1.3. 计算过程中的缓冲区

在任务的计算过程中,还会使用缓冲区来存储计算结果,以避免频繁地向内存中写入数据。而在计算结果需要被输出时,则会使用Shuffle Writer等组件将数据从缓冲区写入到内存或磁盘中。

其他

除此之外,还有一些其他的应用,比如Spark SQL、Spark Streaming、MLlib等。Spark SQL是Spark的一个模块,提供了SQL查询和DataFrame API,可以将SQL查询和RDD转化操作结合起来使用;Spark Streaming是Spark的流处理模块,能够以微批次方式对数据进行处理;MLlib是Spark的机器学习库,提供了许多机器学习算法的实现。

编程模型是Spark Core的核心之一,主要是RDD和算子。RDD是一个弹性分布式数据集合,而算子是对RDD进行操作的方法,分为转化算子和动作算子。

其中,转化算子是将一个RDD转化成另一个RDD的操作,比如map、flatMap、filter、reduceByKey/combineByKey、sortbykey等。而动作算子是对RDD进行求值并返回结果的操作,比如foreach、collect、take、count等。Spark Core还提供了一些控制算子,比如cache、persist、checkpoint、repartition、colase等。

此外,RDD之间还存在依赖关系,分为窄依赖和宽依赖。窄依赖指子RDD中的每个分区最多只依赖于父RDD中的一个分区,而宽依赖指子RDD中的每个分区依赖于父RDD中多个分区。依赖关系与RDD的血统有关,血统是指由哪些RDD转化而来,RDD之间通过血统关系形成一个有向无环图。

在RDD的执行过程中,还会触发shuffle操作,即数据的重新分区。Shuffle操作包括数据的排序、分组、聚合等,它在Spark中的实现是通过MapReduce模型来完成的。

最后,Spark Core的源码是基于standalone模式的,其中资源层由Master和Worker组成,计算层包括SparkContext、DAGScheduler、TaskScheduler、Executor等组件。Spark应用程序的执行流程包括RDD的创建、转化、Action算子的触发、DAG的生成、Task的分发和执行、结果的返回等。