Spark 内核解析

1、Spark内核概述

Spark内核泛指Spark的核心运行机制,包括Spark核心组件的运行机制、Spark任务调度机制、Spark内存管理机制、Spark核心功能的运行原理等,熟练掌握Spark内核原理,能够帮助我们更好的完成Spark代码设计,并能够帮助我们准确锁定项目运行过程中出现的问题的症结所在。

1.1 Spark核心组件回顾

核心组件

1.1.1 Driver

Spark驱动器节点,用于执行Saprk任务中的main方法,负责实际代码的执行工作。

Drievr在Spark作业执行时主要负责:

  1. 将用户程序转换成任务(job)
  2. 在Executor之间调度任务(task)
  3. 跟踪Executor的执行情况
  4. 通过UI展示查询运行情况
1.1.2 Executor

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

Executor有两个核心功能:

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

1.2 Spark通用运行流程概述

运行流程
上图为Spark通用运行流程,不论Spark以何种模式进行部署,都是以如下核心步骤进行工作的:

  1. 任务提交后,都会先启动Driver程序
  2. 随后Driver向集群管理器注册应用程序
  3. 之后集群管理器根据此任务的配置文件分配Executor并启动
  4. Driver所需的资源全部满足后,Driver开始执行main函数,Spark查询为懒执行,当执行到Action算子时开始反向推算,根据宽依赖进行Stage的划分,随后每一个Stage对应一个TaskSet, TaskSet中有多个Task
  5. 根据本地化原则,Task会被分发到执行的Executor去执行,在任务执行的过程中,Executor也会不断地与Drievr进行通信,报告任务运行情况

2、Spark通讯架构

2.1 Spark通信架构概述

Spark2.x版本使用Netty通讯框架作为内部通讯组件。Spark基于Netty新的rpc框架借鉴了Akka中的设计,它是基于Actor模型,如下图所示:
actor
Spark通讯框架中各个组件(Client/Master/Worker)可以认为是一个个独立的实体,各个实体之间通过消息来进行通信,具体各个组件之间的关系图如下:
endpoint
Endpoint(Client/Master/Worker)有一个InBox和N个OutBox (N>=1, N取决于当前Endpoint与多少其他的Endpoint进行通信,一个与其通讯的其他Endpoint对应一个OutBox), Endpoint接收到的消息被写入InBox, 发送出去的消息写入OutBox 并被发送到其他 Endpoint的 InBox 中。

2.2 Saprk通讯架构解析

Spark通讯架构如下图所示:
spark通讯架构

  1. RpcEndpoint: RPC端点,Spark针对每个节点 (Client/Master/Worker) 都称之为一个RPC端点,且都实现RpcEndpoint接口,内部根据不同端点的需求,设计不同的消息和不同的业务处理,如果需要发送(访问) 则调用Dispatcher
  2. RpcEnv: RPC上下文环境,每个RPC端点运行时依赖的上下文环境称之为RpcEnc
  3. Dispatcher: 消息分发器,针对于RPC端点需要发送消息或者从远程RPC接收消息,分发至对应的指令收件箱/发件箱,如果指令接收方是自己则存入收件箱,如果指令接收方不是自己,则放入发件箱
  4. InBox: 指令消息收件箱,一个本地RpcEndpoint对应一个收件箱,Dispatcher在每次向InBox存入消息时,都将对应EndpointData加入内部ReceiverQueue中,另外Dispatcher创建时会启动一个单独线程进行轮询ReceiverQueue, 进行收件箱消息消费
  5. RpcEndpointRef: RpcEndpointRef是对远程RpcEndpoint的一个引用。当我们需要向一个具体的RpcEndpoint发送消息时,一般我们需要获取到该RpcEndpoint的引用,然后通过该引用发送消息。
  6. OutBox: 指令消息发件箱,对于当前RpcEndpoint来说,一个目标RpcEndpoint对应一个发件箱,如果向多个目标RpcEndpoint发送消息,则有多个OutBox。当消息放入OutBox后,紧接着通过TransportClient将消息发送出去,消息放入发件箱以及发送过程是在同一个线程中进行
  7. RpcAddress: 表示远程的RpcEndpointRef的地址, Host + Port
  8. TransportClient: Netty通信客户端,一个OutBox对应一个TransportClient, TransportClient不断轮询OutBox, 根据OutBox消息的receiver信息,请求对应的远程TransportServer
  9. TransportServer: Netty通信服务端,一个RpcEndpoint对应一个TransportServer, 接收远程消息后调用Dispatcher分发消息至对应收/发件箱

根据上面的分析,Spark通信架构的高层视图如下图所示:
通信


2.3 Spark集群启动

Master & Worker启动流程分析
master/worker启动
可以看到,Spark集群采用的消息模式进行通信,也就是EDA架构模式,借助于RPC层的优雅设计,任何两个Endpoint进行通信,发送消息并携带数据即可。上图的流程描述如下所示:

  1. Master启动时首先创建一个RpcEnv对象,负责管理所有通讯逻辑
  2. Master通过RpcEnv对象创建一个Endpoint, Master就是一个Endpoint, Worker可以与其进行通讯
  3. Worker启动时也是创建一个RpcEnv对象
  4. Worker通过RpcEnv对象创建一个Endpoint
  5. Worker通过RpcEnv对象,建立到Master的连接,获取到一个RpcEndpointRef对象,通过该对象可以与Master通信
  6. Worker向Master注册,注册内容包括主机名、端口、CPU Core数量、内存数量
  7. Master接收到Worker的注册,将注册信息维护在内存中的Table中,其中还包含了一个到Worker的RpcEndpointRef对象引用
  8. Master回复Worker已经接收到注册,告知Worker已经注册成功
  9. 此时如果有用户提交Spark程序,Master需要协调启动Driver;而Worker端收到成功注册响应后,开始周期性向Master发送心跳

3、Spark部署模式

Saprk支持三种集群管理器(Cluster manager), 分别为:

  1. Standalone: 独立模式,Spark原生的集群资源管理器,自带完整的服务,可单独部署到一个集群中,无需依赖任何其他资源管理系统,使用Standalone可以很方便地搭建一个集群
  2. Apache Mesos: 一个强大的分布式资源管理框架,它允许多种不同的框架部署在其上,包括yarn
  3. Hadoop Yarn: 统一的资源管理机制,在上面可以运行多套计算框架,如mapreduce, storm等,根据driver在集群中的位置不同,分为yarn-clientyarn-cluster

Spark的运行模式取决于传递给SparkContext的master环境变量的值,个别模式还需要辅助的程序接口来配合使用,目前支持的master字符串以及URL包括:

Master URLMeaning
local在本地运行,只有一个工作进程,无并行计算能力
local[K]在本地运行,有K个工作进程,通常设置K为机器的CPU核心数量
spark://HOST:PORT以Standalone模式运行,这是Spark自身提供的集群运行模式,默认端口号7077
mesos://HOST:PORT在 Mesos集群上运行,Driver进程和Worker进程运行在Mesos集群上,部署模式必须使用固定值:–deploy-mode cluster
yarn-client在Yarn集群上运行,Driever进程在本地,Worker进程在Yarn集群上, 部署模式必须使用固定值:–deploy-mode client。yarn集群地址必须在HADOOP_CONF_DIR or YARN_CONF_DIR变量里定义
yarn-cluster在Yarn集群上运行,Driever进程在Yanr集群上,Worker进程也在Yarn集群上,部署模式必须使用固定值:–deploy-mode cluster。yarn集群地址必须在HADOOP_CONF_DIR or YARN_CONF_DIR变量里定义

用户在提交任务给Saprk处理时,以下两个参数共同决定了Saprk的运行方式:

  • –master MASTER_URL : 决定了Saprk任务提交给哪种集群处理
  • –deploy-mode DEPLOY_MODE : 决定了Driver的运行方式,可选值为Client或者Cluster

3.1 Standalone模式运行机制

Standalone集群有4个重要组成部分,分别是:

  1. Driver: 是一个进程,我们编写的Spark应用程序就运行在Driver上,由Driver进程执行
  2. Master: 是一个进程,主要负责资源的调度和分配,并进行集群的监控等职责
  3. Worker: 是一个进程,一个Worker运行在集群的一台服务器上,主要负责两个职责,一个是用自己的内存存储RDD的某个或某些partition; 另一个是启动其他进程和线程(Executor),对RDD上的partition进行并行的处理和计算
  4. Executor: 是一个进程,一个Worker上可以运行多个Executor, executor通过启动多个线程(task)来执行对RDD的partition进行并行计算,也就是执行我们对RDD定义的例如map、flatMap、reduce等算子操作
3.1.1 Standalone Client模式

scli
Standalone Client模式下,Driver在任务提交的本地机器上运行,Driver启动后向Master注册应用程序,Master根据submit脚本的资源需求找到内部资源至少可以启动一个Executor的所有Worker, 然后在这些Worker之间分配Executor, Worker上的Executor启动后会向Driver反向注册,所有的Executor注册完成之后,Driver开始执行main函数,之后执行到Action算子时,开始划分Stage, 每个Stage生成对应的TaskSet, 之后将task分发到各个Executor上执行。

3.1.2 Standalone Cluster模式

sclu
Standalone cluster模式下,任务提交后,Master会找到一个Worker启动Driver进程,Driver启动后向Master注册应用程序,Master根据submit脚本的资源需求找到内部资源至少可以启动一个Executor的所有Worker,然后在这些Worker之间分配Executor, Worker上的Executor启动后会向Driver反向注册,所有的Executor注册完成后,Driver开始执行main函数,之后执行到Action算子时,开始划分Stage, 每个Stage生成对应的TaskSet, 之后将task分发到各个Executor上执行。

注意: Standalone的两种模式下( client/Cluster) , Master在接到 Driver注册 Spark 应用程序的请求后,会获取其所管理的剩余资源能够启动一个 Executor的所有 Worker, 然后在这些 Worker之间分发 Executor, 此时的分发只考虑 Worker上的资源是否足够使用,直到当前应用程序所需的所有 Executor都分配完毕, Executor反向注册完毕后,Driver开始执行 main 程序。

3.2 Yarn模式运行机制

3.2.1 Yarn Client模式

ycli
YARN Client模式下,Driver在任务提交的本地机器上运行,Driver启动后会和 ResourceManager通讯申请启动 ApplicationMaster, 随后 ResourceManager分配 container , 在 合 适 的 NodeManager上启动 ApplicationMaster,此时的 ApplicationMaster的功能相当于一个 ExecutorLaucher, 只负责向 ResourceManager申请 Executor内存。

ResourceManager接到 ApplicationMaster的资源申请后会分配 container,然后 ApplicationMaster在资源分配指定的 NodeManager上启动 Executor进程,Executor进程启动后会向 Driver反向注册, Executor全部注册完成后 Driver开始执行 main 函数,之后执行到 Action 算子时,触发一个 job,并根据宽依赖开始划分 stage,每个 stage生成对应的 taskSet,之后将 task 分发到各个 Executor上执行。

3.2.2 Yarn Cluster模式

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


4、Spark任务调度机制

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

4.1 Spark 任务提交流程

提交一个 Spark 应用程序, 首先通过 Client 向 ResourceManager请求启动一个Application,同时检查是否有足够的资源满足Application 的需求,如果资源条件满足,则准备 ApplicationMaster的启动上下文,交给 ResourceManager,并循环监控Application 状态。

当提交的资源队列中有资源时, ResourceManager会在某个 NodeManager上启动 ApplicationMaster进程,ApplicationMaster会单独启动 Driver后台线程,当 Driver启动后,ApplicationMaster会通过本地的 RPC 连接 Driver,并开始向 ResourceManager申请 Container 资源运行 Executor进程(一个 Executor 对应与一个 Container),当ResourceManager返回 Container 资源,ApplicationMaster则在对应的 Container 上启动 Executor

Driver线程主要是初始化 SparkContext对象,准备运行所需的上下文, 然后一方面保持与 ApplicationMaster的 RPC 连接,通过 ApplicationMaster申请资源,另一方面根据用户业务逻辑开始调度任务,将任务下发到已有的空闲 Executor上。

ResourceManagerApplicationMaster 返 回 Container 资 源 时 ,ApplicationMaster就尝试在对应的 Container 上启动 Executor进程,Executor进程起来后,会向 Driver反向注册,注册成功后保持与 Driver的心跳,同时等待 Driver分发任务,当分发的任务执行完毕后,将任务状态上报给 Driver

从上述时序图可知,Client 只负责提交 Application 并监控 Application 的状态。对于 Spark 的任务调度主要是集中在两个方面: 资源申请和任务分发,其主要是通过 ApplicationMasterDriver以及 Executor之间来完成。

4.2 Spark 任务调度概述

Driver起来后,Driver则会根据用户程序逻辑准备任务,并根据 Executor资源情况逐步分发任务。在详细阐述任务调度前,首先说明下 Spark 里的几个概念。一个 Spark 应用程序包括 JobStage以及 Task三个概念:

  • Job 是以 Action 方法为界, 遇到一个 Action 方法则触发一个 Job;
  • Stage是 Job 的子集,以 RDD 宽依赖(即 Shuffle)为界,遇到 Shuffle 做一次划分;
  • TaskStage的子集,以并行度(分区数)来衡量,分区数是多少,则有多少个 task。 一个task 对应一个RDD的分区

Spark 的任务调度总体来说分两路进行,一路是 Stage 级的调度,一路是 Task 级的调度,总体调度流程如下图所示:
调度
Spark RDD 通过其 Transactions操作,形成了 RDD 血缘关系图,即 DAG,最后通过 Action 的调用, 触发 Job 并调度执行。DAGScheduler负责 Stage级的调度主要是将 DAG 切分成若干 Stages,并将每个 Stage 打包成 TaskSet交给 TaskScheduler调度。TaskScheduler负责 Task 级的调度,将 DAGScheduler给过来的 TaskSet按照 指定的调度策略分发到 Executor上执行,调度过程中 SchedulerBackend负责提供可用资源,其中 SchedulerBackend有多种实现,分别对接不同的资源管理系统。有了上述感性的认识后,下面这张图描述了 Spark-On-Yarn 模式下在任务调度期间,ApplicationMasterDriver以及 Executor内部模块的交互过程:
交互
Driver初始化 SparkContext过 程 中 , 会 分 别 初 始 化 DAGSchedulerTaskSchedulerSchedulerBackend以及 HeartbeatReceiver,并启动 SchedulerBackend以及 HeartbeatReceiverSchedulerBackend通过 ApplicationMaster申请资源,并不断从 TaskScheduler中拿到合适的 Task 分发到 Executor执行。HeartbeatReceiver负责接收 Executor的心跳信息, 监控 Executor的存活状况, 并通知到 TaskScheduler

4.3 Spark Stage 级调度

Spark 的任务调度是从 DAG 切割开始, 主要是由 DAGScheduler来完成。当遇到一个 Action 操作后就会触发一个 Job 的计算, 并交给 DAGScheduler来提交,下图是涉及到 Job 提交的相关方法调用流程图。
stage调度
Job 由 最 终 的 RDD 和 Action 方 法 封 装 而 成 , SparkContext将 Job 交给 DAGScheduler提交,它会根据 RDD 的血缘关系构成的 DAG 进行切分,将一个 Job 划分为若干 Stages,具体划分策略是,由最终的 RDD 不断通过依赖回溯判断父依赖是否是宽依赖,即以 Shuffle 为界,划分 Stage,窄依赖的 RDD 之间被划分到同一个Stage中,可以进行 pipeline 式的计算,如上图紫色流程部分。划分的 Stages分两类, 一类叫做 ResultStage,为 DAG 最下游的 Stage,由 Action 方法决定,另一类叫做 ShuffleMapStage,为下游 Stage准备数据, 下面看一个简单的例子 WordCount。
wcJob 由 saveAsTextFile 触发,该 Job 由 RDD-3 和 saveAsTextFile 方法组成,根据 RDD 之间的依赖关系从 RDD-3 开始回溯搜索, 直到没有依赖的 RDD-0,在回溯搜索过程中,RDD-3 依赖 RDD-2, 并且是宽依赖, 所以在 RDD-2 和 RDD-3 之间划分Stage,RDD-3 被划到最后一个 Stage,即 ResultStage中,RDD-2 依赖 RDD-1,RDD-1 依赖 RDD-0, 这些依赖都是窄依赖, 所以将 RDD-0、RDD-1 和 RDD-2 划分到同一个 Stage,即 ShuffleMapStage中, 实际执行的时候, 数据记录会一气呵成地执行RDD-0 到 RDD-2 的转化。不难看出, 其本质上是一个深度优先搜索算法。

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

相对来说 DAGScheduler做的事情较为简单,仅仅是在 Stage层面上划分 DAG, 提交 Stage并监控相关状态信息。TaskScheduler则相对较为复杂,下面详细阐述其细节。

4.4 Spark Task 级调度

Spark Task 的调度是由 TaskScheduler来完成,由前文可知,DAGSchedulerStage打包到 TaskSet交给 TaskSchedulerTaskScheduler会将 TaskSet封装为 TaskSetManager加入到调度队列中, TaskSetManager结构如下图所示。
ts
TaskSetManager负责监控管理同一个 Stage中的 TasksTaskScheduler就是以TaskSetManager为单元来调度任务。

前面也提到, TaskScheduler 初始化后会启动 SchedulerBackend, 它负责跟外界打交道,接收 Executor 的注册信息,并维护 Executor 的状态,所以说SchedulerBackend是管“粮食”的,同时它在启动后会定期地去“询问”TaskScheduler 有没有任务要运行,也就是说,它会定期地 “ 问 ”TaskScheduler“ 我有这么余量,你要不要啊 ” ,TaskScheduler 在 SchedulerBackend“问”它的时候, 会从调度队列中按照指定的调度策略选择 TaskSetManager 去调度运行, 大致方法调用流程如下图所示:
sb
图中,将 TaskSetManager加入 rootPool 调度池中之后,调用 SchedulerBackend的 riciveOffers 方法给 driverEndpoint 发送 ReciveOffer 消息; driverEndpoint 收到 ReviveOffer 消息后调用 makeOffers 方法,过滤出活跃状态的 Executor(这些 Executor都是任务启动时反向注册到 Driver 的 Executor),然后将 Executor封装成 WorkerOffer 对象 ; 准 备 好 计 算 资 源 ( WorkerOffer ) 后, taskScheduler基 于 这 些 资 源 调用 resourceOffer 在 Executor上分配 task。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雾岛与鲸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值