热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

任务调度_Spark任务调度机制详解

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Spark任务调度机制详解相关的知识,希望对你有一定的参考价值。Spark

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Spark 任务调度机制详解相关的知识,希望对你有一定的参考价值。




Spark 任务调度机制

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


4.1 Spark 任务提交流程

在上一章中我们讲解了 Spark YARN-Cluster 模式下的任务提交流程,如下图所示:

下面的时序图清晰地说明了一个 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 上。

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

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


4.2 Spark 任务调度概述

当 Driver 起来后,Driver 则会根据用户程序逻辑准备任务,并根据 Executor 资源情况逐步分发任务。在详细阐述任务调度前,首先说明下 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 级的调度,主要是将 DAG 切分成若干 Stages,并将每个 Stage 打包成 TaskSet 交给 TaskScheduler调度。TaskScheduler 负责 Task 级的调度,将 DAGScheduler 给过来的 TaskSet 按照指定的调度策略分发到 Executor 上执行,调度过程中 SchedulerBackend 负责提供可用资源,其中 SchedulerBackend 有多种实现,分别对接不同的资源管理系统。有了上述感性的认识后,下面这张图描述了 Spark-On-Yarn 模式下在任务调度期间,ApplicationMaster、Driver 以及 Executor 内部模块的交互过程:

Driver 初始化 SparkContext 过 程 中 , 会 分 别 初 始 化 DAGScheduler 、TaskScheduler、SchedulerBackend 以及 HeartbeatReceiver,并启动 SchedulerBackend以及HeartbeatReceiver。SchedulerBackend 通过 ApplicationMaster 申请资源,并不断从 TaskScheduler 中拿到合适的 Task 分发到 Executor 执行。HeartbeatReceiver 负责接收 Executor 的心跳信息,监控 Executor 的存活状况,并通知到 TaskScheduler。


4.3 Spark Stage 级调度

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

Job 由 最 终 的 RDD 和 Action 方 法 封 装 而 成 , 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,即 ShuffleMapStage 中,实际执行的时候,数据记录会一气呵成地执行RDD-0 到 RDD-2 的转化。不难看出,其本质上是一个深度优先搜索算法。

一个 Stage 是否被提交,需要判断它的父 Stage 是否执行,只有在父 Stage 执行完毕才能提交当前 Stage,如果一个 Stage 没有父 Stage,那么从该 Stage 开始提交。Stage 提交时会将 Task 信息(分区信息以及方法等)序列化并被打包成 TaskSet 交给TaskScheduler,一个 Partition 对应一个 Task,另一方面 TaskScheduler 会监控 Stage的运行状态,只有 Executor 丢失或者 Task 由于 Fetch 失败才需要重新提交失败的Stage 以调度运行失败的任务,其他类型的 Task 失败会在 TaskScheduler 的调度过程中重试。

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


4.4 Spark Task 级调度

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

TaskSetManager 负责监控管理同一个 Stage 中的 Tasks,TaskScheduler 就是以TaskSetManager 为单元来调度任务。前面也提到,TaskScheduler 初始化后会启动 SchedulerBackend,它负责跟外界打交道,接收 Executor 的注册信息,并维护 Executor 的状态,所以说 SchedulerBackend是管“粮食”的,同时它在启动后会定期地去“询问”TaskScheduler 有没有任务要运行,也就是说, 它会定期地 “ 问 ”TaskScheduler“ 我有这么余量,你 要不要啊 ” ,TaskScheduler 在 SchedulerBackend“问”它的时候,会从调度队列中按照指定的调度策略选择 TaskSetManager 去调度运行,大致方法调用流程如下图所示:

图 3-7 中,将 TaskSetManager 加入 rootPool 调度池中之后,调用 SchedulerBackend的 riviveOffers 方法给 driverEndpoint 发送 ReviveOffer 消息;driverEndpoint 收到ReviveOffer 消息后调用 makeOffers 方法,过滤出活跃状态的 Executor(这些 Executor都是任务启动时反向注册到 Driver 的 Executor),然后将 Executor 封装成 WorkerOffer对 象 ; 准 备 好 计 算 资 源 (WorkerOffer ) 后 , taskScheduler 基 于 这 些 资 源 调 用resourceOffer 在 Executor 上分配 task。


4.4.1 调度策略

前 面 讲 到 , TaskScheduler 会 先 把 DAGScheduler 给 过 来 的 TaskSet 封装成TaskSetManager 扔到任务队列里,然后再从任务队列里按照一定的规则把它们取出来在 SchedulerBackend 给过来的 Executor 上运行。这个调度过程实际上还是比较粗粒度的,是面向 TaskSetManager 的。

TaskScheduler 是以树的方式来管理任务队列,树中的节点类型为 Schdulable,叶子节点为 TaskSetManager,非叶子节点为 Pool,下图是它们之间的继承关系。

TaskScheduler 支持两种调度策略,一种是 FIFO,也是默认的调度策略,另一种是 FAIR。在 TaskScheduler 初始化过程中会实例化 rootPool,表示树的根节点,是Pool 类型。

1. FIFO 调度策略

如果是采用 FIFO 调度策略,则直接简单地将 TaskSetManager 按照先来先到的方式入队,出队时直接拿出最先进队的 TaskSetManager,其树结构如下图所示,TaskSetManager 保存在一个 FIFO 队列中。

2. FAIR 调度策略

FAIR 调度策略的树结构如下图所示:

FAIR 模式中有一个 rootPool 和多个子 Pool,各个子 Pool 中存储着所有待分配的 TaskSetMagager。

在 FAIR 模 式 中 , 需 要 先 对 子 Pool 进 行 排 序 , 再 对 子 Pool 里 面 的TaskSetMagager 进行排序,因为 Pool 和 TaskSetMagager 都继承了 Schedulable 特质,因此使用相同的排序算法。

排序过程的比较是基于 Fair-share 来比较的,每个要排序的对象包含三个属性:runningTasks 值(正在运行的 Task 数)、minShare 值、weight 值,比较时会综合考量 runningTasks 值,minShare 值以及 weight 值。

注意,minShare、weight 的值均在公平调度配置文件 fairscheduler.xml 中被指定,调度池在构建阶段会读取此文件的相关配置。

1) 如果 A 对象的 runningTasks 大于它的 minShare,B 对象的 runningTasks 小于它的 minShare,那么 B 排在 A 前面;(runningTasks 比 minShare 小的先执行)

2) 如果 A、B 对象的 runningTasks 都小于它们的 minShare,那么就比较runningTasks 与 minShare 的比值(minShare 使用率),谁小谁排前面;(minShare使用率低的先执行)

3) 如果 A、B 对象的 runningTasks 都大于它们的 minShare,那么就比较runningTasks 与 weight 的比值(权重使用率),谁小谁排前面。(权重使用率低的先执行)

4) 如果上述比较均相等,则比较名字。整体上来说就是通过 minShare 和 weight 这两个参数控制比较过程,可以做到让 minShare 使用率和权重使用率少(实际运行 task 比例较少)的先运行。

FAIR 模式排序完成后,所有的 TaskSetManager 被放入一个 ArrayBuffer 里,之后依次被取出并发送给 Executor 执行。

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


4.4.2 本地化调度

DAGScheduler 切割 Job,划分 Stage, 通过调用 submitStage 来提交一个 Stage对应的 tasks,submitStage 会调用 submitMissingTasks,submitMissingTasks 确定每个需要计算的 task 的 preferredLocations,通过调用 getPreferrdeLocations()得到partition 的优先位置,由于一个 partition 对应一个 task,此 partition 的优先位置就是 task 的优先位置,对于要提交到TaskScheduler 的 TaskSet 中的每一个 task,该 task优先位置与其对应的 partition 对应的优先位置一致。

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

根据每个 task 的优先位置,确定 task 的 Locality 级别,Locality 一共有五种,优先级由高到低顺序:

在调度执行时,Spark 调度总是会尽量让每个 task 以最高的本地性级别来启动,当一个 task 以 X 本地性级别启动,但是该本地性级别对应的所有节点都没有空闲资源而启动失败,此时并不会马上降低本地性级别启动而是在某个时间长度内再次以X 本地性级别来启动该 task,若超过限时时间则降级启动,去尝试下一个本地性级别,依次类推。

可以通过调大每个类别的最大容忍延迟时间,在等待阶段对应的 Executor 可能就会有相应的资源去执行此 task,这就在在一定程度上提到了运行性能。


4.4.3 失败重试与黑名单机制

除了选择合适的 Task 调度运行外,还需要监控 Task 的执行状态,前面也提到,与外部打交道的是 SchedulerBackend,Task 被提交到 Executor 启动执行后,Executor会将执行状态上报给 SchedulerBackend,SchedulerBackend 则告诉 TaskScheduler,TaskScheduler 找到该 Task 对应的 TaskSetManager,并通知到该 TaskSetManager,这样 TaskSetManager 就知道 Task 的失败与成功状态,对于失败的 Task,会记录它失败的次数,如果失败次数还没有超过最大重试次数,那么就把它放回待调度的 Task池子中,否则整个 Application 失败。

在记录 Task 失败次数过程中,会记录它上一次失败所在的 Executor Id 和 Host,这样下次再调度这个 Task 时,会使用黑名单机制,避免它被调度到上一次失败的节点上,起到一定的容错作用。黑名单记录 Task 上一次失败所在的 Executor Id 和 Host,以及其对应的“拉黑”时间,“拉黑”时间是指这段时间内不要再往这个节点上调度这个 Task 了。

 


推荐阅读
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 一面自我介绍对象相等的判断,equals方法实现。可以简单描述挫折,并说明自己如何克服,最终有哪些收获。职业规划表明自己决心,首先自己不准备继续求学了,必须招工作了。希望去哪 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 项目需要将音视频文件上传服务器,考虑并发要求高,通过七牛来实现。直接上代码usingQiniu.IO;usingQiniu.IO.Resumable;usingQiniu.RPC; ... [详细]
  • Hadoop 源码学习笔记(4)Hdfs 数据读写流程分析
    Hdfs的数据模型在对读写流程进行分析之前,我们需要先对Hdfs的数据模型有一个简单的认知。数据模型如上图所示,在NameNode中有一个唯一的FSDirectory类负责维护文件 ... [详细]
  • .babelrc是用来设置转码规则和插件的,这种文件在window上无法直接创建,也无法在HBuilder中创建,甚至无法查看,但可以在sublimetext中创建、查看并编辑。当 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 本文介绍了在Windows系统下安装Python、setuptools、pip和virtualenv的步骤,以及安装过程中需要注意的事项。详细介绍了Python2.7.4和Python3.3.2的安装路径,以及如何使用easy_install安装setuptools。同时提醒用户在安装完setuptools后,需要继续安装pip,并注意不要将Python的目录添加到系统的环境变量中。最后,还介绍了通过下载ez_setup.py来安装setuptools的方法。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 本文介绍了一道经典的状态压缩题目——关灯问题2,并提供了解决该问题的算法思路。通过使用二进制表示灯的状态,并枚举所有可能的状态,可以求解出最少按按钮的次数,从而将所有灯关掉。本文还对状压和位运算进行了解释,并指出了该方法的适用性和局限性。 ... [详细]
  • 数据结构与算法的重要性及基本概念、存储结构和算法分析
    数据结构与算法在编程领域中的重要性不可忽视,无论从事何种岗位,都需要掌握数据结构和算法。本文介绍了数据结构与算法的基本概念、存储结构和算法分析。其中包括线性结构、树结构、图结构、栈、队列、串、查找、排序等内容。此外,还介绍了图论算法、贪婪算法、分治算法、动态规划、随机化算法和回溯算法等高级数据结构和算法。掌握这些知识对于提高编程能力、解决问题具有重要意义。 ... [详细]
author-avatar
橙子小狸
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有