Spark的任务提交和调度


1、Spark简介

Spark 是一种基于内存的快速、通用、可扩展的大数据分析计算引擎。2009年诞生于加州大学伯克利分校AMPLab,2010年开源,2013年6月成为Apache孵化项目,2014年2月成为Apache顶级项目。项目由Scala开发。

Spark 核心模块
在这里插入图片描述

  1. Spark Core
    Spark Core 中提供了 Spark 最基础与最核心的功能,Spark 其他的功能如:Spark SQL, Spark Streaming,GraphX, MLlib 都是在 Spark Core 的基础上进行扩展的;

  2. Spark SQL
    Spark SQL 是Spark 用来操作结构化数据的组件。通过 Spark SQL,用户可以使用 SQL或者Apache Hive 版本的 SQL 方言(HQL)来查询数据;

  3. Spark Streaming
    Spark Streaming 是 Spark 平台上针对实时数据进行流式计算的组件,提供了丰富的处理数据流的API;

  4. Spark MLlib
    MLlib 是 Spark 提供的一个机器学习算法库。MLlib 不仅提供了模型评估、数据导入等额外的功能,还提供了一些更底层的机器学习原语;

  5. Spark GraphX
    GraphX 是 Spark 面向图计算提供的框架与算法库。

部署模式

  1. Standalone:独立模式,Spark 原生的简单集群管理器,自带完整的服务,可单独部署到一个集群中,无需依赖任何其他资源管理系统,使用 Standalone 可以很方便地搭建一个集群;
  2. Hadoop YARN:统一的资源管理机制,在上面可以运行多套计算框架,如 MR、Storm 等。根据 Driver 在集群中的位置不同,分为 yarn client(集群外)和 yarn cluster(集群内部)
  3. Apache Mesos:一个强大的分布式资源管理框架,它允许多种不同的框架部署在其上, 包括 Yarn。
  4. K8S : 容器式部署环境。

实际上,除了上述这些通用的集群管理器外,Spark 内部也提供了方便用户测试和学习的本地集群部署模式和 Windows 环境。由于在实际工厂环境下使用的绝大多数的集群管理器是Hadoop YARN,因此我们后面关注的重点以 Hadoop YARN 模式下的 Spark 集群部署。


2、Spark核心组件

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

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

  1. 将用户程序转化为作业(Job);
  2. 在 Executor 之间调度任务(Task);
  3. 跟踪Executor 的执行情况;
  4. 通过UI 展示查询运行情况;

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

Executor 有两个核心功能:

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

3、运行流程概述

这只是一个大概的流程,主要想表达的是 计算和资源管理 走两条分支。在Drive线程资源满足后,才会执行黄色分支的计算流程,进行作业的调度和任务的执行,详细内容请看后面。

在这里插入图片描述

  1. 任务提交后,都会先启动 Driver 程序;

  2. 随后Driver 向集群管理器注册应用程序;

  3. 之后集群管理器根据此任务的配置文件分配Executor 并启动;

  4. Driver 开始执行 main 函数,Spark 查询为懒执行,当执行到 Action 算子时开始反向推算,根据宽依赖进行 Stage 的划分,随后每一个 Stage 对应一个Taskset,Taskset 中有多个 Task,查找可用资源Executor 进行调度;

  5. 根据本地化原则,Task 会被分发到指定的 Executor 去执行,在任务执行的过程中,Executor 也会不断与Driver 进行通信,报告任务运行情况。


4、任务提交流程

4.1 SparkSubmit进程解析

先从一个简单任务开始

下面是提交一个spark任务的命令,调用 spark-submit 脚本

bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master local[2] \
./examples/jars/spark-examples_2.12-3.0.0.jar \
10

查看spark-submit,在最后一行,调用spark-class脚本,把SparkSubmit 类和输入的参数都作为spark-class的参数。
在这里插入图片描述
查看 spark-class,同样在最后一行,执行 exec "${CMD[@]}"
在这里插入图片描述

为了看清spark-class中执行的内容,在前一行加上 echo “${CMD[@]}” 进行内容的打印;提交上面的任务命令,查看运行结果的首行

在这里插入图片描述
可以看出java调用了一个类,为了看的更清楚,将参数除去剩下了:

java org.apache.spark.deploy.SparkSubmit

所以,提交任务时,实际上启动了一个 SparkSubmit 的JVM进程

4.2 YARN Cluster 模式

流程图解

在这里插入图片描述

详细流程

  1. 执行脚本提交任务,实际是启动一个 SparkSubmit 的 JVM 进程;

  2. SparkSubmit 类中的 main 方法反射调用 YarnClusterApplication 的 main 方法;

  3. YarnClusterApplication 创建 Yarn 客户端,然后向 Yarn 服务器发送执行指令:bin/java ApplicationMaster

  4. Yarn 框架收到指令后会在指定的 NM 中启动ApplicationMaster;

  5. ApplicationMaster 启动 Driver 线程,Drive通过程序的main反射初始化SparkText后,再执行用户的应用程序;【而此时计算流程是堵塞的,就是运行流程概述中的黄色分支流程,需要等待获取资源】

  6. AM的YarnRMClient 向 RM 注册,申请资源;

  7. 获取资源后 AM 向NM 发送指令:bin/java YarnCoarseGrainedExecutorBackend;

  8. CoarseGrainedExecutorBackend 进程会接收消息,跟 Driver 通信,注册已经启动的Executor;然后启动计算对象 Executor 等待接收任务;

  9. Driver 线程继续执行完成作业的调度和任务的执行;【资源准备好后,计算流程就可以进行了】

  10. Driver 分配任务并监控任务的执行。

4.3 YARN Client 模式

流程图解
在这里插入图片描述

  1. 执行脚本提交任务,实际是启动一个 SparkSubmit 的 JVM 进程;

  2. SparkSubmit 类中的 main 方法反射调用用户代码的main 方法;

  3. 启动Driver 线程,执行用户的作业,并创建 ScheduleBackend;

  4. YarnClientSchedulerBackend 向RM 发送指令:bin/java ExecutorLauncher;

  5. Yarn 框架收到指令后会在指定的 NM 中启动 ExecutorLauncher实际上还是调用ApplicationMaster 的 main 方法);

    object ExecutorLauncher {
    	def main(args: Array[String]): Unit = { ApplicationMaster.main(args)
    	}
    }
    
  6. AM 向 RM 注册,申请资源;

  7. 获取资源后 AM 向NM 发送指令:bin/java CoarseGrainedExecutorBackend;

  8. CoarseGrainedExecutorBackend 进程会接收消息,跟 Driver 通信,注册已经启动的Executor;然后启动计算对象 Executor 等待接收任务;

  9. Driver 分配任务并监控任务的执行。

可以看出,Yarn cluster和Yarn Client最主要的区别是,Drive线程的位置不同。前者在集群中,后者不在集群中。

4.4 Standalone Cluster 模式

在这里插入图片描述
在 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 上执行。

4.5 Standalone Client 模式

在这里插入图片描述

在 Standalone Client 模式下,Driver 在任务提交的本地机器上运行。

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


5、任务调度机制

5.1 任务调度概述

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

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

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

  3. Task 是 Stage 的子集,以最后一个RDD并行度(分区数)来衡量,分区数是多少,则有多少个 task。

Spark 的任务调度总体来说分两路进行,一路是 Stage 级的调度,一路是 Task 级的调度,总体调度流程如下图所示:
在这里插入图片描述

Spark RDD 通过其Transactions 操作,形成了RDD 血缘(依赖)关系图,即 DAG,最后通过 Action 的调用,触发 Job 并调度执行,执行过程中会创建两个调度器:DAGScheduler TaskScheduler

  • DAGScheduler 负责 Stage 级的调度,主要是将 job 切分成若干 Stages,并将每个 Stage
    打包成 TaskSet 交给 TaskScheduler 调度。【阶段划分、阶段的任务切分】

  • TaskScheduler 负责Task 级的调度,将 DAGScheduler 给过来的TaskSet 按照指定的调度策略分发到 Executor 上执行,调度过程中 SchedulerBackend 负责提供可用资源,其中SchedulerBackend 有多种实现,分别对接不同的资源管理系统。

5.2 Spark Stage 级调度

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

提交的相关方法调用流程图。
在这里插入图片描述

  1. Job 由最终的 RDD 和 Action 方法封装而成;

  2. SparkContext 将 Job 交给 DAGScheduler 提交,它会根据 RDD 的血缘关系构成的 DAG 进行切分,将一个 Job 划分为若干 Stages。具体划分策略是:由最终的 RDD 不断通过依赖回溯判断父依赖是否是宽依赖(即以 Shuffle 为界)划分 Stage,窄依赖的 RDD 之间被划分到同一个 Stage 中,可以进行pipeline 式的计算。划分的 Stages 分两类,一类叫做 ResultStage ,为 DAG 最下游的 Stage ,由 Action 方法决定;另一类叫做ShuffleMapStage,为下游 Stage 准备数据。

下面看一个简单的例子 WordCount。

在这里插入图片描述
Job 由 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,形成 pipeline 操作,。即
ShuffleMapStage 中,实际执行的时候,数据记录会一气呵成地执行 RDD-0 到 RDD-2 的转化。不难看出,其本质上是一个深度优先搜索(Depth First Search)算法。

Spark中的阶段的划分等于shuffle依赖数量+ 1

DAGScheduler 在 Stage 层面上划分 DAG,提交Stage 并监控相关状态信息。

5.3 Spark Task 级调度

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

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

TaskScheduler 初始化后会启动 SchedulerBackend,它负责跟外界打交道, 接收 Executor 的注册信息,并维护 Executor 的状态;所以说 SchedulerBackend 是管“粮食” 的,同时它在启动后会定期地去“询问”TaskScheduler 有没有任务要运行,也就是说,它会定期地“问”TaskScheduler“我有这么余粮,你要不要啊”,TaskScheduler 在 SchedulerBackend“问”它的时候,会从调度队列中按照指定的调度策略选择 TaskSetManager 去调度运行。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值