前言

  Spark会将用户提交的作业看作一个job,在提交的时候首先将job转换为一系列的RDD,并按照RDD之间的依赖关系构建DAG(有向无环图),DAGScheduler会按照RDD依赖的不同将DAG划分为不同的stage,每个stage内部按照RDD分区数创建多个task,最后将task封装成taskSets发送到TaskScheduler调度执行。

  RDD依赖

  窄依赖(NarrowDependency):下游RDD与上游RDD的分区是一一对应的关系;

  宽依赖(ShuffleDependency):下游RDD的一个分区对应上游RDD的多个分区。

  Partitioner

  如果RDD间的依赖关系是Shuffle依赖,spark通过分区计算器partitioner来确定上游RDD的分区数据输出到下游RDD的哪个分区。

  spark大部分API默认采用HashPartitioner,将key的hashCode值与分区数进行取模运算后得到该key的分区下标;HashPartitioner的劣势:Java中数组的hashCode是基于数组对象本身的,如果key的数据类型为数组,数组内容一样的key可能会被分到不同的分区。

  RangePartitioner:先从整个RDD中抽取样本并排序取得一个Array[key]类型的数组变量rangeBounds,判断key在rangeBounds中的范围,得到该key在下游RDD中分区的下标。

  当然也可以自定义partitioner,写一个类继承自抽象类Partitioner并重写numPartitions、getPartition和equals方法即可。

  DAGScheduler

  DAGScheduler会将DAG中的RDD按照依赖类型划分为不同的stage,并构建这些stage的依赖关系,让没有依赖的stage并行计算,有依赖的stage串行运算,提升资源使用率和运行效率;stage分为最下游的ResultStage和需要shuffle的ShuffleMapStage。

  1、JobListener & JobWaiter & ActiveJob & DAGSchedulerEventProcessLoop

  特质JobListener用于对作业中每个task执行结果进行监听,JobWaiter实现了JobListener并最终确定作业的成功或失败;ActiveJob表示已经激活的Job;DAGSchedulerEventProcessLoop是DAGScheduler内部的事件循环处理器,spark组件都通过向DAGScheduler投递DAGSchedulerEvent来使用DAGScheduler,DAGSchedulerEventProcessLoop将处理这些Event并调用DAGScheduler的不同方法,它内部有三个方法:doOnReceive()用于调用DAGScheduler的方法处理不同的事件、onError()和onStop()。

  2、提交Job

  spark程序中每触发一次action算子,就会提交一次job,job会被转换成一系列RDD并调用DAGScheduler.runJob方法,内部调用submitJob方法。

def runJob[T, U](
rdd: RDD[T],
func: (TaskContext, Iterator[T]) => U,
partitions: Seq[Int],
callSite: CallSite,
resultHandler: (Int, U) => Unit,
properties: Properties): Unit = {
//生成运行job的启动时间
val start = System.nanoTime
//提交job,submitJob是异步执行的,所以会立刻返回JobWaiter对象
val waiter = submitJob(rdd, func, partitions, callSite, resultHandler, properties)
//等待job处理,如果成功打印日志,如果失败打印日志并抛出异常
ThreadUtils.awaitReady(waiter.completionFuture, Duration.Inf)
waiter.completionFuture.value.get match {
case scala.util.Success(_) =>
logInfo("Job %d finished: %s, took %f s".format
(waiter.jobId, callSite.shortForm, (System.nanoTime - start) / 1e9))
case scala.util.Failure(exception) =>
logInfo("Job %d failed: %s, took %f s".format
(waiter.jobId, callSite.shortForm, (System.nanoTime - start) / 1e9))
// SPARK-8644: Include user stack trace in exceptions coming from DAGScheduler.
val callerStackTrace = Thread.currentThread().getStackTrace.tail
exception.setStackTrace(exception.getStackTrace ++ callerStackTrace)
throw exception
}
}

  submitJob方法内部根据job创建jobWaiter,并向DAGSchedulerEventProcessLoop发送jobSubmitted事件。

def submitJob[T, U](
rdd: RDD[T],
func: (TaskContext, Iterator[T]) => U,
partitions: Seq[Int],
callSite: CallSite,
resultHandler: (Int, U) => Unit,
properties: Properties): JobWaiter[U] = {
// Check to make sure we are not launching a task on a partition that does not exist.
//获取当前job的最大分区数并检查,如果有不存在的分区则抛出异常
val maxPartitions = rdd.partitions.length
partitions.find(p => p >= maxPartitions || p < ).foreach { p =>
throw new IllegalArgumentException(
"Attempting to access a non-existent partition: " + p + ". " +
"Total number of partitions: " + maxPartitions)
}
//生成下一个job的ID
val jobId = nextJobId.getAndIncrement()
//如果job的分区数为0,创建一个totalTasks属性为0的JobWaiter并返回,JobWaiter会将jobPromise设置为Success表示job处理成功
if (partitions.size == ) {
// Return immediately if the job is running 0 tasks
return new JobWaiter[U](this, jobId, , resultHandler)
}
//如果job的分区数大于0,创建一个等待执行完成的JobWaiter,向DAGSchedulerEventProcessLoop发送JobSubmited事件
assert(partitions.size > )
val func2 = func.asInstanceOf[(TaskContext, Iterator[_]) => _]
val waiter = new JobWaiter(this, jobId, partitions.size, resultHandler)
eventProcessLoop.post(JobSubmitted(
jobId, rdd, func2, partitions.toArray, callSite, waiter,
SerializationUtils.clone(properties)))
//返回JobWaiter对象
waiter
}

  DAGSchedulerEventProcessLoop接收到JobSubmited事件后会调用DAGScheduler的handleJobSubmited方法,创建ResultStage并提交,实现如下

private[scheduler] def handleJobSubmitted(jobId: Int,
finalRDD: RDD[_],
func: (TaskContext, Iterator[_]) => _,
partitions: Array[Int],
callSite: CallSite,
listener: JobListener,
properties: Properties) {
var finalStage: ResultStage = null
//创建ResultStage,期间可能会发生异常,比如运行在HadoopRDD上的任务依赖的HDFS文件被删除了,异常发生时需要调用JobWaiter的jobFailed方法
try {
finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite)
} catch {
...
} // Job submitted, clear internal data.
barrierJobIdToNumTasksCheckFailures.remove(jobId)
//创建ActiveJob
val job = new ActiveJob(jobId, finalStage, callSite, listener, properties)
//清除cacheLocs缓存中RDD的分区位置信息
clearCacheLocs()
logInfo("Got job %s (%s) with %d output partitions".format(
job.jobId, callSite.shortForm, partitions.length))
logInfo("Final stage: " + finalStage + " (" + finalStage.name + ")")
logInfo("Parents of final stage: " + finalStage.parents)
logInfo("Missing parents: " + getMissingParentStages(finalStage)) //生成job提交的时间
val jobSubmissionTime = clock.getTimeMillis()
//将jobID与刚创建的activeJob之间的对应关系放入jobIdToActiveJob中
jobIdToActiveJob(jobId) = job
//将刚创建的activeJob放入activeJobs集合
activeJobs += job
//让finalStage的_activeJob属性持有刚创建的activeJob
finalStage.setActiveJob(job)
//获取当前job的所有stage对应的stageinfo
val stageIds = jobIdToStageIds(jobId).toArray
val stageInfos = stageIds.flatMap(id => stageIdToStage.get(id).map(_.latestInfo))
//向LiveListenerBus投递sparkListenerJobStart事件,缓存stageId
listenerBus.post(
SparkListenerJobStart(job.jobId, jobSubmissionTime, stageInfos, properties))
//提交ResultStage
submitStage(finalStage)
}

 3、构建Stage

private def createResultStage(
rdd: RDD[_],
func: (TaskContext, Iterator[_]) => _,
partitions: Array[Int],
jobId: Int,
callSite: CallSite): ResultStage = {
//RDD的一些初始检查
checkBarrierStageWithDynamicAllocation(rdd)
checkBarrierStageWithNumSlots(rdd)
checkBarrierStageWithRDDChainPattern(rdd, partitions.toSet.size)
//获取所有父stage的列表
val parents = getOrCreateParentStages(rdd, jobId)
//生成resultStage的标识
val id = nextStageId.getAndIncrement()
//创建ResultStage
val stage = new ResultStage(id, rdd, func, partitions, parents, jobId, callSite)
//将ResultStage与它的ID对应关系添加到stageIdToStage缓存中
stageIdToStage(id) = stage
//更新job的身份标识与ResultStage的映射关系
updateJobIdStageIdMaps(jobId, stage)
stage
} //获取或创建给定RDD的所有父stage,这些stage将被分配给jobId对应的job
private def getOrCreateParentStages(rdd: RDD[_], firstJobId: Int): List[Stage] = {
getShuffleDependencies(rdd).map { shuffleDep =>
getOrCreateShuffleMapStage(shuffleDep, firstJobId)
}.toList
} //获取给定RDD直接父类的shuffle依赖
private[scheduler] def getShuffleDependencies(
rdd: RDD[_]): HashSet[ShuffleDependency[_, _, _]] = {
val parents = new HashSet[ShuffleDependency[_, _, _]]
val visited = new HashSet[RDD[_]]
val waitingForVisit = new ArrayStack[RDD[_]]
waitingForVisit.push(rdd)
while (waitingForVisit.nonEmpty) {
val toVisit = waitingForVisit.pop()
if (!visited(toVisit)) {
visited += toVisit
toVisit.dependencies.foreach {
case shuffleDep: ShuffleDependency[_, _, _] =>
parents += shuffleDep
case dependency =>
waitingForVisit.push(dependency.rdd)
}
}
}
parents
} private def getOrCreateShuffleMapStage(
shuffleDep: ShuffleDependency[_, _, _],
firstJobId: Int): ShuffleMapStage = {
shuffleIdToMapStage.get(shuffleDep.shuffleId) match {
//如果已经创建了shullfeDependecy对应的shullfeMapStage,则直接返回shullfeMapStage
case Some(stage) =>
stage
//否则调用getMissingAncestorShuffleDependencies找到所有未创建shuffleMapStage的shullfeDependecy
//并调用createShuffleMapStage创建shullfeMapStage并注册
case None =>
getMissingAncestorShuffleDependencies(shuffleDep.rdd).foreach { dep =>
if (!shuffleIdToMapStage.contains(dep.shuffleId)) {
createShuffleMapStage(dep, firstJobId)
}
}
// 最后为当前shullfeDependecy创建shullfeMapStage并注册
createShuffleMapStage(shuffleDep, firstJobId)
}
} private def getMissingAncestorShuffleDependencies(
rdd: RDD[_]): ArrayStack[ShuffleDependency[_, _, _]] = {
val ancestors = new ArrayStack[ShuffleDependency[_, _, _]]
val visited = new HashSet[RDD[_]]
// We are manually maintaining a stack here to prevent StackOverflowError
// caused by recursively visiting
val waitingForVisit = new ArrayStack[RDD[_]]
waitingForVisit.push(rdd)
while (waitingForVisit.nonEmpty) {
val toVisit = waitingForVisit.pop()
if (!visited(toVisit)) {
visited += toVisit
getShuffleDependencies(toVisit).foreach { shuffleDep =>
if (!shuffleIdToMapStage.contains(shuffleDep.shuffleId)) {
ancestors.push(shuffleDep)
waitingForVisit.push(shuffleDep.rdd)
} // Otherwise, the dependency and its ancestors have already been registered.
}
}
}
ancestors
} def createShuffleMapStage(shuffleDep: ShuffleDependency[_, _, _], jobId: Int): ShuffleMapStage = {
//获取shuffleDependecy的rdd属性作为将要创建shuffleMapStage的rdd
val rdd = shuffleDep.rdd
//rdd初始检查
checkBarrierStageWithDynamicAllocation(rdd)
checkBarrierStageWithNumSlots(rdd)
checkBarrierStageWithRDDChainPattern(rdd, rdd.getNumPartitions)
//获取rdd的分区数组的长度作为要创建的shullfeMapStage的task的数量
val numTasks = rdd.partitions.length
//获取将要创建的shullfeMapStage的所有父stage
val parents = getOrCreateParentStages(rdd, jobId)
//生成将要创建的shullfeMapStage的身份标识
val id = nextStageId.getAndIncrement()
//创建shullfeMapStage
val stage = new ShuffleMapStage(
id, rdd, numTasks, parents, jobId, rdd.creationSite, shuffleDep, mapOutputTracker) //更新刚创建的shuffleMapStage的映射关系
stageIdToStage(id) = stage
shuffleIdToMapStage(shuffleDep.shuffleId) = stage
updateJobIdStageIdMaps(jobId, stage) //mapStatus的检查,因为stage可以重试,所以当前stage可能已经执行过,有部分map任务可能执行成功并将mapStatus更新到
//mapOutputTrackerMaster的缓存,当前stage只要复制这些mapStatus即可,避免重复计算
if (!mapOutputTracker.containsShuffle(shuffleDep.shuffleId)) {
// Kind of ugly: need to register RDDs with the cache and map output tracker here
// since we can't do it in the RDD constructor because # of partitions is unknown
logInfo("Registering RDD " + rdd.id + " (" + rdd.getCreationSite + ")")
mapOutputTracker.registerShuffle(shuffleDep.shuffleId, rdd.partitions.length)
}
stage
}

 4、提交ResultStage

  submitStage方法会通过入参ResultStage逐层获取父stage,再从最上游stage开始逐步调用TaskScheduler.submitTasks方法提交task集合,最后才提交ResultStage的task集合。

private def submitStage(stage: Stage) {
//获取当前stage对应的jobId
val jobId = activeJobForStage(stage)
//判断获取到的jobId是否能找到对应的ActiveJob
if (jobId.isDefined) {
logDebug("submitStage(" + stage + ")")
//如果当前stage还未提交
if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {
//获取当前stage的所有未提交的父stage
val missing = getMissingParentStages(stage).sortBy(_.id)
logDebug("missing: " + missing)
//如果没有未提交的父stage,就提交当前stage中所有未提交的task;否则先提交所有未提交的fustage,并将当前stage加入waitingSrages集合
if (missing.isEmpty) {
logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents")
submitMissingTasks(stage, jobId.get)
} else {
for (parent <- missing) {
submitStage(parent)
}
waitingStages += stage
}
}
} else { //如果没有找到当前stage对应的ActiveJob,终止依赖于当前stage的所有Job
abortStage(stage, "No active job for stage " + stage.id, None)
}
}

  

Spark2.4.0源码——DAGScheduler的更多相关文章

  1. spark最新源码下载并导入到开发环境下助推高质量代码(Scala IDEA for Eclipse和IntelliJ IDEA皆适用)(以spark2.2.0源码包为例)(图文详解)

    不多说,直接上干货! 前言   其实啊,无论你是初学者还是具备了有一定spark编程经验,都需要对spark源码足够重视起来. 本人,肺腑之己见,想要成为大数据的大牛和顶尖专家,多结合源码和操练编程. ...

  2. Spark-2.4.0源码:sparkContext

    在看sparkContext之前,先回顾一下Scala的语法.Scala构造函数分主构造和辅构造函数,辅构造函数是关键字def+this定义的,而类中不在方法体也不在辅构造函数中的代码就是主构造函数, ...

  3. 【Spark2.0源码学习】-1.概述

          Spark作为当前主流的分布式计算框架,其高效性.通用性.易用性使其得到广泛的关注,本系列博客不会介绍其原理.安装与使用相关知识,将会从源码角度进行深度分析,理解其背后的设计精髓,以便后续 ...

  4. spark2.0源码学习

    [Spark2.0源码学习]-1.概述 [Spark2.0源码学习]-2.一切从脚本说起 [Spark2.0源码学习]-3.Endpoint模型介绍 [Spark2.0源码学习]-4.Master启动 ...

  5. Spark2.0.0源码编译

    Hive默认使用MapReduce作为执行引擎,即Hive on mr,Hive还可以使用Tez和Spark作为其执行引擎,分别为Hive on Tez和Hive on Spark.由于MapRedu ...

  6. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  7. AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking

    AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...

  8. AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking

    我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...

  9. AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager

    让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...

随机推荐

  1. 5亿英镑!Imagination同意出售给私募机构Canyon Bridge

    集微网最新消息,据外媒 FT 报道,Imagination Technologies Group Plc(以下简称“Imagination”)同意由中国背景的私募基金Canyon Bridge Cap ...

  2. WPF与缓动(二) 正弦与余弦缓动

    原文:WPF与缓动(二) 正弦与余弦缓动   WPF与缓动(二) 正弦与余弦缓动                                                             ...

  3. Token的设计(2)

    词法分析 Token的几个种类 前端的第一步就是词法分析, 这个过程通俗来讲就是将源代码转化为一串Tokens. 所以首先应该想到的是, 到底该有哪几种类型的Token ? 关于这个问题我已经想过了, ...

  4. js 操作样式

    <!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...

  5. 检查Android是否支持指纹识别以及是否已经录入指纹

    原文:检查Android是否支持指纹识别以及是否已经录入指纹 Android M 开始,系统中加入了指纹相关功能. 主要用到的类为:FingerprintManager 只提供三个方法: 返回值 方法 ...

  6. 浅谈Android高通(Qualcomm)和联发科(MTK)平台

    一款CPU好不好是要从多个方面考虑的,并不是说简简单单看一个主频.几个核心数就完了,更重要的是它的综合实力到底有多强,这里面当然也会牵扯到价格问题,性能相似当然是便宜的获胜,这是毋庸置疑的. 事实上, ...

  7. 活锁(livelock) 专题

    活锁(livelock) 活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败. 活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于 ...

  8. 《芒果TV》UWP版利用Windows10通用平台特性,率先支持Xbox One平台

    在Windows开发者中心开放提交Xbox平台应用之后,<芒果TV>UWP版迅速更新v3.1.2版,通过升级兼容目标,利用Windows10通用平台特性,率先覆盖Xbox平台用户. 芒果T ...

  9. Android零基础入门第33节:Android事件处理概述

    原文:Android零基础入门第33节:Android事件处理概述 通过对Android基本组件的学习,也有接触少部分Android的事件处理,比如按钮的点击事件.选框的状态切换事件. 一.Andro ...

  10. LINQ学习笔记(一)

    LINQ,语言集成查询(Language Integrated Query)是一组用于C#和Visual Basic语言的扩展. 它允许编写C#或Visual Basic代码以查询数据库相同的方法操作 ...