作者:刘旭晖 Raymond 转载请注明出处

Email:colorant at 163.com

BLOG:http://blog.csdn.net/colorant/

在Spark中作业调度的相关类最重要的就是DAGScheduler,DAGScheduler顾名思义就是基于DAG图的Scheduler

DAG全称 Directed
Acyclic
Graph。有向无环图。

简单的来说。就是一个由顶点和有方向性的边构成的图中,从随意一个顶点出发,没有不论什么一条路径会将其带回到出发的顶点。

在作业调度系统中。调度的基础就在于推断多个作业任务的依赖关系,这些任务之间可能存在多重的依赖关系,也就是说有些任务必须先获得运行,然后另外的相关依赖任务才干运行,可是任务之间显然不应该出现不论什么直接或间接的循环依赖关系,所以本质上这样的关系适合用DAG有向无环图来表示。

概括地描写叙述DAGScheduler和TaskScheduler(关于TaskScheduler的相关细节,在我之前的关于Spark执行模式的文章中有)的功能划分就是:TaskScheduler负责实际每一个详细任务的物理调度。DAGScheduler负责将作业拆分成不同阶段的具有依赖关系的多批任务。能够理解为DAGScheduler负责任务的逻辑调度。

基本概念

Task任务
:单个分区数据集上的最小处理流程单元

TaskSet任务集:一组关联的,可是互相之间没有Shuffle依赖关系的任务所组成的任务集

Stage调度阶段:一个任务集所相应的调度阶段

Job作业:一次RDD Action生成的一个或多个Stage所组成的一次计算作业

执行方式

DAGScheduler在SparkContext初始化过程中实例化,一个SparkContext相应一个DAGScheduler,DAGScheduler的事件循环逻辑基于Akka
Actor的消息传递机制来构建,在DAGScheduler的Start函数中创建了一个eventProcessActor用来处理各种DAGSchedulerEvent,这些事件包含作业的提交,任务状态的变化,监控等等

private[scheduler]case class JobSubmitted(
jobId: Int,
finalRDD: RDD[_],
func: (TaskContext, Iterator[_]) => _,
partitions: Array[Int],
allowLocal: Boolean,
callSite: String,
listener: JobListener,
properties: Properties = null)
extends DAGSchedulerEvent private[scheduler]case class JobCancelled(jobId: Int) extends DAGSchedulerEvent
private[scheduler]case class JobGroupCancelled(groupId: String) extends DAGSchedulerEvent
private[scheduler]case object AllJobsCancelled extends DAGSchedulerEvent
private[scheduler]
case classBeginEvent(task: Task[_], taskInfo: TaskInfo) extends DAGSchedulerEvent private[scheduler]
case classGettingResultEvent(task: Task[_], taskInfo: TaskInfo) extends DAGSchedulerEvent private[scheduler]case class CompletionEvent(
task: Task[_],
reason: TaskEndReason,
result: Any,
accumUpdates: Map[Long, Any],
taskInfo: TaskInfo,
taskMetrics: TaskMetrics)
extends DAGSchedulerEvent private[scheduler]case class ExecutorAdded(execId: String, host: String) extendsDAGSchedulerEvent
private[scheduler]case class ExecutorLost(execId: String) extends DAGSchedulerEvent
private[scheduler] caseclass TaskSetFailed(taskSet: TaskSet, reason: String) extends DAGSchedulerEvent
private[scheduler]case object ResubmitFailedStages extends DAGSchedulerEvent
private[scheduler]case object StopDAGScheduler extends DAGSchedulerEvent

不论是Client还是TaskScheduler与DAGScheduler的交互方式基本上都是通过DAGScheduler暴露的函数接口间接的给eventProcessActor发送相关消息

如前面所说,DAGScheduler最重要的任务之中的一个就是计算作业和任务的依赖关系,制定调度逻辑

DAGScheduler作业调度的两个主要入口是submitJob
和 runJob,两者的差别在于前者返回一个Jobwaiter对象,能够用在异步调用中。用来推断作业完毕或者取消作业,runJob在内部调用submitJob。堵塞等待直到作业完毕(或失败)

详细往DAGScheduler提交作业的操作,基本都是封装在RDD的相关Action操作里面,不须要用户显式的提交作业

用户代码都是基于RDD的一系列计算操作,实际执行时,这些计算操作是Lazy执行的,并非全部的RDD操作都会触发Spark往Cluster上提交实际作业,基本上仅仅有一些须要返回数据或者向外部输出的操作才会触发实际计算工作,其他的变换操作基本上仅仅是生成相应的RDD记录依赖关系。

DAGScheduler内部维护了各种 task / stage / job之间的映射关系表

工作流程

提交并执行一个Job的基本流程,包含下面步骤

划分Stage

当某个操作触发计算,向DAGScheduler提交作业时,DAGScheduler须要从RDD依赖链最末端的RDD出发,遍历整个RDD依赖链,划分Stage任务阶段,并决定各个Stage之间的依赖关系。Stage的划分是以ShuffleDependency为根据的,也就是说当某个RDD的运算须要将数据进行Shuffle时。这个包括了Shuffle依赖关系的RDD将被用来作为输入信息,构建一个新的Stage,由此为根据划分Stage。可以确保有依赖关系的数据可以依照正确的顺序得到处理和运算。

以GroupByKey操作为例。该操作返回的结果实际上是一个ShuffleRDD,当DAGScheduler遍历到这个ShuffleRDD的时候,由于其Dependency是一个ShuffleDependency。于是这个ShuffleRDD的父RDD以及shuffleDependency等对象就被用来构建一个新的Stage,这个Stage的输出结果的分区方式,则由ShuffleDependency中的Partitioner对象来决定。

能够看到,虽然划分和构建Stage的根据是ShuffleDependency。相应的RDD也就是这里的ShuffleRDD,可是这个Stage所处理的数据是从这个shuffleRDD的父RDD開始计算的,仅仅是终于的输出结果的位置信息參考了ShuffleRDD返回的ShuffleDependency里所包括的内容。而shuffleRDD本身的运算操作(事实上就是一个获取shuffle结果的过程),是在下一个Stage里进行的。

生成Job,提交Stage

上一个步骤得到一个或多个有依赖关系的Stage,当中直接触发Job的RDD所关联的Stage作为FinalStage生成一个Job实例。这两者的关系进一步存储在resultStageToJob映射表中。用于在该Stage所有完毕时做一些兴许处理。如报告状态,清理Job相关数据等。

详细提交一个Stage时,首先推断该Stage所依赖的父Stage的结果是否可用。假设全部父Stage的结果都可用,则提交该Stage,假设有不论什么一个父Stage的结果不可用,则迭代尝试提交父Stage。
全部迭代过程中因为所依赖Stage的结果不可用而没有提交成功的Stage都被放到waitingStages列表中等待将来被提交

什么时候waitingStages中的Stage会被又一次提交呢,当一个属于中间过程Stage的任务(这样的类型的任务所相应的类为ShuffleMapTask)完毕以后,DAGScheduler会检查相应的Stage的全部任务是否都完毕了。假设是都完毕了。则DAGScheduler将又一次扫描一次waitingStages中的全部Stage,检查他们是否还有不论什么依赖的Stage没有完毕,假设没有就能够提交该Stage。

此外每当完毕一次DAGScheduler的事件循环以后,也会触发一次从等待和失败列表中扫描并提交就绪Stage的调用过程

任务集的提交

每一个Stage的提交,终于是转换成一个TaskSet任务集的提交,DAGScheduler通过TaskScheduler接口提交TaskSet,这个TaskSet终于会触发TaskScheduler构建一个TaskSetManager的实例来管理这个TaskSet的生命周期。对于DAGScheduler来说提交Stage的工作到此就完毕了。而TaskScheduler的详细实现则会在得到计算资源的时候,进一步通过TaskSetManager调度详细的Task到相应的Executor节点上进行运算

任务作业完毕状态的监控

要保证相互依赖的job/stage可以得到顺利的调度运行,DAGScheduler就必定须要监控当前Job
/ Stage乃至Task的完毕情况。这是通过对外(主要是对TaskScheduler)暴露一系列的回调函数来实现的,对于TaskScheduler来说。这些回调函数主要包含任务的開始结束失败。任务集的失败,DAGScheduler依据这些Task的生命周期信息进一步维护Job和Stage的状态信息。

此外TaskScheduler还能够通过回调函数通知DAGScheduler详细的Executor的生命状态,假设某一个Executor崩溃了。或者因为不论什么原因与Driver失去联系了,则相应的Stage的shuffleMapTask的输出结果也将被标志为不可用,这也将导致相应Stage状态的变更,进而影响相关Job的状态,再进一步可能触发相应Stage的又一次提交来又一次计算获取相关的数据。

任务结果的获取

一个详细的任务在Executor中运行完成以后,其结果须要以某种形式返回给DAGScheduler,依据任务类型的不同,任务的结果的返回方式也不同

对于FinalStage所相应的任务(相应的类为ResultTask)返回给DAGScheduler的是运算结果本身,而对于ShuffleMapTask,返回给DAGScheduler的是一个MapStatus对象。MapStatus对象管理了ShuffleMapTask的运算输出结果在BlockManager里的相关存储信息。而非结果本身。这些存储位置信息将作为下一个Stage的任务的获取输入数据的根据

而依据任务结果的大小的不同,ResultTask返回的结果又分为两类。假设结果足够小。则直接放在DirectTaskResult对象内,假设超过特定尺寸(默认约10MB)则在Executor端会将DirectTaskResult先序列化,再把序列化的结果作为一个Block存放在BlockManager里,而后将BlockManager返回的BlockID放在IndirectTaskResult对象中返回给TaskScheduler。TaskScheduler进而调用TaskResultGetter将IndirectTaskResult中的BlockID取出并通过BlockManager终于取得相应的DirectTaskResult。

当然从DAGScheduler的角度来说,这些过程对它来说是透明的。它所获得的都是任务的实际运算结果。

TaskSetManager

前面提到DAGScheduler负责将一组任务提交给TaskScheduler以后,这组任务的调度工作对它来说就算完毕了。接下来这组任务内部的调度逻辑,则是由TaskSetManager来完毕的。

TaskSetManager的主要接口包含:

ResourceOffer依据TaskScheduler所提供的单个Resource资源包含host。executor和locality的要求返回一个合适的Task。TaskSetManager内部会依据上一个任务成功提交的时间。自己主动调整自身的Locality匹配策略,假设上一次成功提交任务的时间间隔非常长,则减少对Locality的要求(比如从最差要求Process
Local减少为最差要求Node Local),反之则提高对Locality的要求。这一动态调整Locality策略基本能够理解为是为了提高任务在最佳Locality的情况下得到执行的机会,由于Resource资源可能是在短期内分批提供给TaskSetManager的,动态调整Locality门槛有助于改善总体的Locality分布情况。

举个样例。假设TaskSetManager内部有a/b两个任务等待调度,a/b两个任务Prefer的节点各自是Host
A 和 Host B,
这时候先有一个Host C的资源以最差匹配为Rack Local的形式提供给TaskSetManager。假设没有内部动态Locality调整机制。那么比方a任务将被调度。

接下来在非常短的时间间隔内,一个Host
A的资源来到,相同的b任务被调度。
而原本最佳的情况应该是任务b调度给Host C,
而任务a调度给Host A。

当然动态Locality也会带来一定的调度延迟,因此怎样设置合适的调整策略也是须要针对实际情况来确定的。眼下能够设置參数包含

spark.locality.wait.process

spark.locality.wait.node

spark.locality.wait.rack

即各个Locality级别中TaskSetManager等待分配下一个任务的时间,假设距离上一次成功分配资源的时间间隔超过相应的參数值,则减少匹配要求(即process
-> node -> rack -> any),
而每当成功分配一个任务时,则重置时间间隔,并更新Locality级别为当前成功分配的任务的Locality级别

handleSuccessfulTask / handleFailedTask /handleTaskGettingResult
:用于更新任务的执行状态,Taskset Manager在这些函数中除了更新自身维护的任务状态列表等信息,用于剩余的任务的调度以外,也会进一步调用DAGScheduler的函数接口将结果通知给它。

此外,TaskSetManager在调度任务时还可能进一步考虑Speculation的情况,亦即当某个任务的执行时间超过其他任务的执行完毕时间的一个特定比例值时,该任务可能被反复调度。目的当然是为了防止某个执行中的Task因为某些特殊原因(比如所在节点CPU负载过高,IO带宽被占等等)执行特别缓慢迟延了整个Stage的完毕时间,Speculation相同须要依据集群和作业的实际情况合理配置。否则可能反而减少集群性能。

Pool
调度池

前面我们说了,DAGScheduler负责构建具有依赖关系的任务集,TaskSetManager负责在详细的任务集的内部调度任务。而TaskScheduler负责将资源提供给TaskSetManager供其作为调度任务的根据。可是每一个SparkContext可能同一时候存在多个可执行的任务集(没有依赖关系),这些任务集之间怎样调度,则是由调度池(POOL)对象来决定的,Pool所管理的对象是下一级的Pool或者TaskSetManager对象

TaskSchedulerImpl在初始化过程中会依据用户设定的SchedulingMode(默觉得FIFO)创建一个rootPool根调度池,之后依据详细的调度模式再进一步创建SchedulableBuilder对象,详细的SchedulableBuilder对象的BuildPools方法将在rootPool的基础上完毕整个Pool的构建工作。

眼下的实现有两种调度模式,相应了两种类型的Pool:

FIFO:先进先出型,FIFO Pool直接管理的是TaskSetManager,每一个TaskSetManager创建时都存储了其相应的StageID。FIFO
pool终于依据StageID的顺序来调度TaskSetManager

FAIR:公平调度,FAIR Pool管理的对象是下一级的POOL,或者TaskSetManager。公平调度的基本原则是依据所管理的Pool/TaskSetManager中正在执行的任务的数量来推断优先级。用户能够设置minShare最小任务数。weight任务权重来调整相应Pool里的任务集的优先程度。当採用公平调度模式时,眼下所构建的调度池是两级的结构,即根调度池管理一组子调度池,子调度池进一步管理属于该调度池的TaskSetManager

公平调度模式的配置通过配置文件来管理,默认使用fairscheduler.xml文件。范例參见conf文件夹下的模板:

<?xmlversion="1.0"?

>
<allocations>
<pool name="production">
<schedulingMode>FAIR</schedulingMode>
<weight>1</weight>
<minShare>2</minShare>
</pool>
<pool name="test">
<schedulingMode>FIFO</schedulingMode>
<weight>2</weight>
<minShare>3</minShare>
</pool>
</allocations>

因为这里的调度池是在SparkContext内部的调度。因此其调度范畴是一个基于该SparkContext的Spark应用程序,正常情况下,多个Spark应用程序之间在调度池层面是没有调度优先级关系的。

那么这样的调度模式的应用场合是如何的呢?
举一个样例就是SparkServer或者SharkServer,作为一个长期执行的SparkContext,他们代理执行了其他连上Server的Spark应用的任务,这样你能够为每一个链接依照username指定一个Pool执行,从而实现用户优先级和资源分配的合理调度等。

Spark应用之间的调度

前面提到调度池仅仅是在SparkContxt内部调度资源,SparkContext之间的调度关系,依照Spark不同的执行模式,就不一定归Spark所管理的了。

在Mesos和YARN模式下。底层资源调度系统的调度策略由Mesos和YARN所决定。仅仅有在Standalone模式下,Spark
Master依照当前cluster资源是否满足等待列表中的Spark应用
对内存和CPU资源的需求,而决定是否创建一个SparkContext相应的Driver,进而完毕Spark应用的启动过程,这能够粗略近似的觉得是一种粗颗粒度的有条件的FIFO策略吧

spark internal - 作业调度的更多相关文章

  1. Spark核心作业调度和任务调度之DAGScheduler源码

    前言:本文是我学习Spark 源码与内部原理用,同时也希望能给新手一些帮助,入道不深,如有遗漏或错误的,请在原文评论或者发送至我的邮箱 tongzhenguotongzhenguo@gmail.com ...

  2. Spark internal - 多样化的运行模式(上)

    Spark的运行模式多种多样,在单机上既可以以本地模式运行,也可以以伪分布式模式运行.而当以分布式的方式运行在Cluster集群中时,底层的资源调度可以使用Mesos 或者是Hadoop Yarn , ...

  3. Spark internal - 多样化的运行模式 (下)

    Spark的各种运行模式虽然启动方式,运行位置,调度手段有所不同,但它们所要完成的任务基本都是一致的,就是在合适的位置安全可靠的根据用户的配置和Job的需要管理和运行Task,这里粗略的列举一下在运行 ...

  4. java.lang.NoSuchMethodError: org.apache.spark.internal.Logging.$init$(Lorg/apache/spark/internal/Logging;)V

    1.sparkML的版本不对应 请参考官网找到对于版本, 比如我的 spark2.3.3          spark MLlib 也是2.3.3

  5. spark总结——转载

    转载自:    spark总结 第一个Spark程序 /** * 功能:用spark实现的单词计数程序 * 环境:spark 1.6.1, scala 2.10.4 */ // 导入相关类库impor ...

  6. Spark 学习总结

    摘要: 1.spark_core 2.spark_sql 3.spark_ml 内容: 1.spark_core 原理篇: Spark RDD 核心总结 RangePartitioner 实现简记 S ...

  7. spark的sparkUI如何解读?

    spark的sparkUI如何解读? 以spark2.1.4来做例子 Job - schedule mode 进入之后默认是进入spark job 页面 这个说明有很详细的解释,spark有两种操作算 ...

  8. Spark2.3(四十三):Spark Broadcast总结

    为什么要使用广播(broadcast)变量? Spark中因为算子中的真正逻辑是发送到Executor中去运行的,所以当Executor中需要引用外部变量时,需要使用广播变量.进一步解释: 如果exe ...

  9. 大数据Spark+Kafka实时数据分析案例

    本案例利用Spark+Kafka实时分析男女生每秒购物人数,利用Spark Streaming实时处理用户购物日志,然后利用websocket将数据实时推送给浏览器,最后浏览器将接收到的数据实时展现, ...

随机推荐

  1. linux 下查看二进制文件

    查看二进制有以下几种方法: 方法一:hexdump apt-get install libdata-hexdumper-perl 安装好之后就可以直接hexdump your_binary_file ...

  2. 洛谷—— P2934 [USACO09JAN]安全出行Safe Travel || COGS ——279|| BZOJ——1576

    https://www.luogu.org/problem/show?pid=2934 题目描述 Gremlins have infested the farm. These nasty, ugly ...

  3. 洛谷 P1898 缘分计算

    P1898 缘分计算 题目描述 缘分是一个外国人难以理解的中文名词.大致说来,缘分是一种冥冥中将两人(通常是情人)结合的力量.仅管这是种迷信,很多人——特别是女生——喜欢去计算它. 不幸的是,644 ...

  4. c++动态库中使用命名空间的问题

    这是C++才会有的语言特性. 假如你使用一个程序库,他里面有桓霰淞拷衋bc,可是你自己也不小心定义了一个叫abc的变量,这样就会引起重定义错误.所以为了避免这样的现象,C++引入了名字空间(names ...

  5. Codeforces 11B Jumping Jack(数学)

    B. Jumping Jack time limit per test 1 second memory limit per test 64 megabytes input standard input ...

  6. 29.AngularJS 简介

    转自:https://www.cnblogs.com/best/tag/Angular/ AngularJS 是一个 JavaScript 框架.它可通过 <script> 标签添加到 H ...

  7. 特性Attibute定义和使用

    1.定义特性 [AttributeUsage(AttributeTargets.Class,AllowMultiple = false,Inherited = false)] public class ...

  8. 圈复杂度(Cyclomatic Complexity)

    圈复杂度(Cyclomatic Complexity)是很常用的一种度量软件代码复杂程度的标准.这里所指的“代码复杂程度”并非软件内在业务逻辑的复杂程度,而是指代码的实现方式的 复杂程度.说起来有点绕 ...

  9. 自定义分页控件-基于Zhifeiya的分页控件改版

    基于Zhifeiya的分页控件改版的分页. html显示代码: <div class="pagelist"> {{.pagerHtml}} </div> c ...

  10. [工具] UltraEdit使用技巧汇总

    ltraEdit是一套功能强大的文本编辑器,可以编辑文本.十六进制.ASCII码,可以取代记事本,内建英文单字检查.C++及VB指令突显,可同时编辑多个文件,而且即使开启很大的文件速度也不会慢.说到编 ...