spark内核解析

本文深入解析Spark的内存管理,包括堆内和堆外内存的分配策略,以及静态和统一内存管理模式。此外,详细阐述了Spark的任务调度机制,包括Job、Stage和Task的划分以及FIFO和FAIR调度策略。还介绍了Shuffle过程中的任务个数、HashShuffle和SortShuffle的优化。最后,讲解了核心组件BlockManager、DiskStore、MemoryStore和BlockTransferService的功能,以及Broadcast Variable和Accumulator的作用。
摘要由CSDN通过智能技术生成

1、spark部署模式

Spark的运行模式取决于传递给SparkContext的MASTER环境变量的值,目前包括:

本地运行:local、local[K]、local[*]

Standalone模式运行:spark://HOST:PORT

Mesos集群上运行:mesos://HOST:PORT

Yarn集群上运行:

yarn-client:Driver进程在本地,Executor进程在Yarn集群上,部署模式必须使用固定值:--deploy-mode client。

yarn-cluster:Driver进程在Yarn集群上,Work进程也在Yarn集群上,部署模式必须使用固定值:--deploy-mode cluster。

–master MASTER_URL :决定了Spark任务提交给哪种集群处理。

–deploy-mode DEPLOY_MODE:决定了Driver的运行方式,可选值为Client或者Cluster。

2、YARN Cluster模式

在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上执行。

3、Spark 通讯架构

Spark2.x版本使用Netty通讯框架作为内部通讯组件。spark 基于netty新的rpc框架借鉴了Akka的中的设计,它是基于Actor模型。

4、Spark 任务调度机制

在工厂环境下,Spark集群的部署方式一般为YARN-Cluster模式,之后的内核分析内容中我们默认集群的部署方式为YARN-Cluster模式。

1)Spark任务调度概述

一个Spark应用程序包括Job、Stage以及Task三个概念:

Job是以Action方法为界,遇到一个Action方法则触发一个Job;

Stage是Job的子集,以RDD宽依赖(即Shuffle)为界,遇到Shuffle做一次划分;

Task是Stage的子集,以并行度(分区数)来衡量,分区数是多少,则有多少个task;

Spark的任务调度总体来说分两路进行,一路是Stage级的调度,一路是Task级的调度

Spark RDD通过其Transactions操作,形成了RDD血缘关系图,即DAG,最后通过Action的调用,触发Job并调度执行。DAGScheduler负责Stage级的调度,主要是将job切分成若干Stages,并将每个Stage打包成TaskSet交给TaskScheduler调度。TaskScheduler负责Task级的调度,调度过程中SchedulerBackend负责提供可用资源。

Driver初始化SparkContext过程中,会分别初始化DAGScheduler、TaskScheduler、SchedulerBackend以及HeartbeatReceiver。

2)Spark Stage级调度

Spark的任务调度是从DAG切割开始,主要是由DAGScheduler来完成。当遇到一个Action操作后就会触发一个Job的计算,并交给DAGScheduler来提交。

Job由最终的RDD和Action方法封装而成,SparkContext将Job交给DAGScheduler提交,它会根据RDD的血缘关系构成的DAG进行切分,将一个Job划分为若干Stages,具体划分策略是,由最终的RDD不断通过依赖回溯判断父依赖是否是宽依赖,即以Shuffle为界,划分Stage,窄依赖的RDD之间被划分到同一个Stage中。

划分的Stages分两类,一类叫做ResultStage,为DAG最下游的Stage,由Action方法决定,另一类叫做ShuffleMapStage,为下游Stage准备数据。

实际执行的时候,数据记录会一气呵成地执行RDD-0到RDD-2的转化。

一个Stage是否被提交,需要判断它的父Stage是否执行,只有在父Stage执行完毕才能提交当前Stage,如果一个Stage没有父Stage,那么从该Stage开始提交。

3)Spark Task级调度

Spark Task的调度是由TaskScheduler来完成,由前文可知,DAGScheduler将Stage打包到TaskSet交给TaskScheduler,TaskScheduler会将TaskSet封装为TaskSetManager加入到调度队列中,TaskSetManager结构如下图所示。

TaskSetManager负责监控管理同一个Stage中的Tasks,TaskScheduler就是以TaskSetManager为单元来调度任务。

TaskScheduler在SchedulerBackend“问”它的时候,会从调度队列中按照指定的调度策略选择TaskSetManager去调度运行

调度策略

TaskScheduler会先把DAGScheduler给过来的TaskSet封装成TaskSetManager扔到任务队列里,然后再从任务队列里按照一定的规则把它们取出来在SchedulerBackend给过来的Executor上运行

TaskScheduler支持两种调度策略,一种是FIFO,也是默认的调度策略,另一种是FAIR

从调度队列中拿到TaskSetManager后,那么接下来的工作就是TaskSetManager按照一定的规则一个个取出task给TaskScheduler,TaskScheduler再交给SchedulerBackend去发到Executor上执行。前面也提到,TaskSetManager封装了一个Stage的所有task,并负责管理调度这些task。

在调度执行时,Spark调度总是会尽量让每个task以最高的本地性级别来启动。

5、Spark Shuffle解析

在划分stage时,最后一个stage称为finalStage,它本质上是一个ResultStage对象,前面的所有stage被称为ShuffleMapStage。

ShuffleMapStage的结束伴随着shuffle文件的写磁盘

1)Shuffle中的任务个数

Spark Shuffle分为map阶段和reduce阶段,或者称之为ShuffleRead阶段和ShuffleWrite阶段,那么对于一次Shuffle,map过程和reduce过程都会由若干个task来执行,当执行到Shuffle操作时,map端的task个数和partition个数一致。reduce端的stage默认取spark.default.parallelism这个配置项的值作为分区数,如果没有配置,则以map端的最后一个RDD的分区数作为其分区数(也就是N),那么分区数就决定了reduce端的task的个数。

HashShuffle解析

未经优化的:下一个stage的task有多少个,当前stage的每个task就要创建多少份磁盘文件

优化的:下一批task就会复用之前已有的shuffleFileGroup,包括其中的磁盘文件,也就是说,此时task会将数据写入已有的磁盘文件中,而不会写入新的磁盘文件中

SortShuffle解析

SortShuffleManager的运行机制主要分成两种,一种是普通运行机制,另一种是bypass运行机制。当shuffle read task的数量小于等于spark.shuffle.sort. bypassMergeThreshold参数的值时(默认为200),就会启用bypass机制。

普通运行机制

由于一个task就只对应一个磁盘文件,也就意味着该task为下游stage的task准备的数据都在这一个文件中,因此还会单独写一份索引文件,其中标识了下游各个task的数据在文件中的start offset与end offset。

bypass运行机制

bypass运行机制的触发条件如下:

1)shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值。

2)不是聚合类的shuffle算子。

而该机制与普通SortShuffleManager运行机制的不同在于:第一,磁盘写机制不同;第二,不会进行排序。也就是说,启用该机制的最大好处在于,shuffle write过程中,不需要进行数据的排序操作,也就节省掉了这部分的性能开销。

6、Spark 内存管理

1)堆内内存

堆内内存的大小,由 Spark 应用程序启动时的 –executor-memory 或 spark.executor.memory 参数配置,缓存 RDD 数据和广播(Broadcast)数据时占用的内存被规划为存储(Storage)内存,而这些任务在执行 Shuffle 时占用的内存被规划为执行(Execution)内存,剩余的部分不做特殊规划,那些 Spark 内部的对象实例,或者用户定义的 Spark 应用程序中的对象实例,均占用剩余的空间。

有时Spark 并不能准确记录实际可用的堆内内存,从而也就无法完全避免内存溢出(OOM, Out of Memory)的异常。

2)堆外内存

可以直接在工作节点的系统内存中开辟空间,存储经过序列化的二进制数据。堆外内存之所以能够被精确的申请和释放,是由于内存的申请和释放不再通过JVM机制,而是直接向操作系统申请。

内存空间的分配

1)静态内存管理

存储内存、执行内存和其他内存的大小在 Spark 应用程序运行期间均为固定的,但用户可以应用程序启动前进行配置

2)统一内存管理

存储内存和执行内存共享同一块空间,可以动态占用对方的空闲区域

双方的空间都不足时,则存储到硬盘;若己方空间不足而对方空余时,可借用对方的空间;

存储内存管理

凭借血统,Spark 保证了每一个 RDD 都可以被重新恢复。

如果一个 RDD 上要执行多次行动,可以在第一次行动中使用 persist 或 cache 方法,在内存或磁盘中持久化或缓存这个 RDD,从而在后面的行动时提升计算速度。

cache 方法是使用默认的 MEMORY_ONLY 的存储级别将 RDD 持久化到内存,故缓存是一种特殊的持久化

默认的存储级别都是仅在内存存储一份

在对 RDD 持久化时,Spark 规定了 MEMORY_ONLY、MEMORY_AND_DISK 等 7 种不同的存储级别

MEMORY_ONLY:以非序列化的Java对象的方式持久化在JVM内存中

MEMORY_AND_DISK:同上,但是当某些partition无法存储在内存中时,会持久化到磁盘中。

MEMORY_ONLY_SER:同MEMORY_ONLY,但是会使用Java序列化方式,将Java对象序列化后进行持久化。

MEMORY_AND_DISK_SER:同MEMORY_AND_DISK,但是使用序列化方式持久化Java对象

DISK_ONLY:使用非序列化Java对象的方式持久化,完全存储到磁盘上

MEMORY_ONLY_2、MEMORY_AND_DISK_2:如果是尾部加了2的持久化级别,表示将持久化数据复用一份

而存储级别是以下 5 个变量的组合:

class StorageLevel private(

private var _useDisk: Boolean, //磁盘

private var _useMemory: Boolean, //这里其实是指堆内内存

private var _useOffHeap: Boolean, //堆外内存

private var _deserialized: Boolean, //是否为非序列化

private var _replication: Int = 1 //副本个数

)

RDD的缓存过程

RDD 在缓存到存储内存之后,Partition 被转换成 Block

执行内存管理

执行内存主要用来存储任务在执行 Shuffle 时占用的内存,Shuffle 是按照一定规则对 RDD 数据重新分区的过程,主要是对Shuffle 的 Write 和 Read 两阶段分析

6、核心组件解析

每个节点上都有一个BlockManager,BlockManager中有3个非常重要的组件:

  • DiskStore:负责对磁盘数据进行读写;
  • MemoryStore:负责对内存数据进行读写;
  • BlockTransferService:负责建立BlockManager到远程其他节点的BlockManager的连接,负责对远程其他节点的BlockManager的数据进行读写

共享变量

一种是Broadcast Variable广播变量),另一种是Accumulator累加变量

Broadcast Variable是只读的,并且在每个Executor上只会有一个副本,而不会为每个task都拷贝一份副本。

Accumulator是仅仅被相关操作累加的变量,因此可以在并行中被有效地支持。它们可用于实现计数器(如MapReduce)或总和计数。Accumulator存在于Driver端,从节点读取不到Accumulator的数值task只能对Accumulator进行累加操作,不能读取它的值,只有Driver程序可以读取Accumulator的值。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值