Spark Scheduler 模块(上)
// Create and start the scheduler
val (sched, ts) = SparkContext.createTaskScheduler(this, master)
_schedulerBackend = sched
_taskScheduler = ts
_dagScheduler = new DAGScheduler(this)
SparkContext.createTaskScheduler 方法中,依据 master 的不同,分别创建不同的 _taskScheduler,比如 master=local 对应的是类 TaskSchedulerImpl,master=yarn-cluster对应的是org.apache.spark.scheduler.cluster.YarnClusterScheduler。因为 TaskScheduler 需要运行在资源调度器(YARN, Mesos, Local)上,所以需要分别实现。而 DAGScheduler 是 Spark 自身的逻辑,只有一种实现即可。这种设计上的分离也是值得我们学习的。
private[scheduler] val eventProcessLoop = new DAGSchedulerEventProcessLoop(this)
JobSubmitted
CompletionEvent
StageCancelled
JobCancelled
Job 到 Stage 的完整流程
当 SparkContext.runJob 调用了 DAGScheduler.runJob 后,传入的参数是 RDD。DAGScheduler.runJob 什么都没做,又把 RDD 传入了 DAGScheduler.submitJob:
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] = {
val jobId = nextJobId.getAndIncrement()
val waiter = new JobWaiter(this, jobId, partitions.size, resultHandler)
eventProcessLoop.post(JobSubmitted(
jobId, rdd, func2, partitions.toArray, callSite, waiter,
SerializationUtils.clone(properties)))
waiter
}
submitJob() 创建了一个独立的 jobId,一个传出去的 waiter,然后把发送了一个事件 JobSubmitted 给 eventProcessLoop: DAGSchedulerEventProcessLoop。上文提到这是一个异步的消息队列。于是函数调用者(DAGScheduler.runJob)就使用 waiter 等待消息即可。
当 eventProcessLoop: DAGSchedulerEventProcessLoop 接受到事件 JobSubmitted,然后调用 DAGScheduler.handleJobSubmitted
private[scheduler] def handleJobSubmitted(jobId: Int,
finalRDD: RDD[_],
func: (TaskContext, Iterator[_]) => _,
partitions: Array[Int],
callSite: CallSite,
listener: JobListener,
properties: Properties) {
var finalStage: ResultStage = null
finalStage = newResultStage(finalRDD, partitions.length, jobId, callSite)
if (finalStage != null) {
val job = new ActiveJob(jobId, finalStage, func, partitions, callSite, listener, properties)
activeJobs += job
finalStage.resultOfJob = Some(job)
submitStage(finalStage)
}
submitWaitingStages()
}
首先创建了 finalStage,然后 sumbitStage 把它提交。submitWaitingStages 是提交以前失败的、现在又满足条件的作业。这里的核心是 submitStage:
/** Submits stage, but first recursively submits any missing parents. */
private def submitStage(stage: Stage) {
val jobId = activeJobForStage(stage)
if (jobId.isDefined) {
logDebug("submitStage(" + stage + ")")
if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {
val missing = getMissingParentStages(stage).sortBy(_.id)
logDebug("missing: " + missing)
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 {
abortStage(stage, "No active job for stage " + stage.id, None)
}
}
private def getMissingParentStages(stage: Stage): List[Stage] = {
val missing = new HashSet[Stage]
val visited = new HashSet[RDD[_]]
val waitingForVisit = new Stack[RDD[_]]
def visit(rdd: RDD[_]) {
if (!visited(rdd)) {
visited += rdd
val rddHasUncachedPartitions = getCacheLocs(rdd).contains(Nil)
if (rddHasUncachedPartitions) {
for (dep <- rdd.dependencies) {
dep match {
case shufDep: ShuffleDependency[_, _, _] =>
val mapStage = getShuffleMapStage(shufDep, stage.firstJobId)
if (!mapStage.isAvailable) {
missing += mapStage
}
case narrowDep: NarrowDependency[_] =>
waitingForVisit.push(narrowDep.rdd)
}
}
}
}
}
waitingForVisit.push(stage.rdd)
while (waitingForVisit.nonEmpty) {
visit(waitingForVisit.pop())
}
missing.toList
}
这里的 parent stage 是通过 rdd 的依赖关系递归遍历获得的。对于宽依赖(Shuffle Dependency),Spark 会产生新的 mapStage作为 finalStage 的一个 missingParent,对于窄依赖(Narrow Dependency),Spark 不会产生新的 stage。这里对于 stage 的划分就是实现了论文中的方式。这种划分方法,好处在于快速恢复。如果一个 rdd 的 partition 丢失,该 partition 如果只有窄依赖,则其 parent rdd 也只需要计算相应的一个 partition 就能实现数据恢复。
正是这种 stage 的划分策略,才有了所谓的 DAG 图。当 stage 的 DAG 产生以后,会按树状结构,拓扑有序的执行一个个 stage,即当父 stage 都完成计算后,才可以进行子 stage 的计算:
/** Called when stage's parents are available and we can now do its task. */
private def submitMissingTasks(stage: Stage, jobId: Int) {
var taskBinary: Broadcast[Array[Byte]] = null
try {
// 根据不同的 stage,生成相应的 task 的二进制内容,广播出去
val taskBinaryBytes: Array[Byte] = stage match {
case stage: ShuffleMapStage =>
closureSerializer.serialize((stage.rdd, stage.shuffleDep): AnyRef).array()
case stage: ResultStage =>
closureSerializer.serialize((stage.rdd, stage.resultOfJob.get.func): AnyRef).array()
}
taskBinary = sc.broadcast(taskBinaryBytes)
} catch {
// ..
} // 一个 stage 产生的 task 数目等于 partition 数目
val tasks: Seq[Task[_]] = try {
stage match {
case stage: ShuffleMapStage =>
partitionsToCompute.map { id =>
val locs = taskIdToLocations(id)
val part = stage.rdd.partitions(id)
new ShuffleMapTask(stage.id, stage.latestInfo.attemptId,
taskBinary, part, locs, stage.internalAccumulators)
}
case stage: ResultStage =>
val job = stage.resultOfJob.get
partitionsToCompute.map { id =>
val p: Int = job.partitions(id)
val part = stage.rdd.partitions(p)
val locs = taskIdToLocations(id)
new ResultTask(stage.id, stage.latestInfo.attemptId,
taskBinary, part, locs, id, stage.internalAccumulators)
}
}
} catch {
//..
} // 把这个 stage 对应的 tasks 提交到 taskSchduler 上
if (tasks.size > ) {
taskScheduler.submitTasks(new TaskSet(
tasks.toArray, stage.id, stage.latestInfo.attemptId, stage.firstJobId, properties))
stage.latestInfo.submissionTime = Some(clock.getTimeMillis())
}
}
至此,job 在 DAGScheduler 里完成了 stage DAG 图的构建,stage 到 tasks 组的转换,最后提交到 taskScheduler 上。当 task 被执行完毕,DAGScheduler.handleTaskCompletion 会被调用:
private[scheduler] def handleTaskCompletion(event: CompletionEvent) {
val task = event.task
val stageId = task.stageId
event.reason match {
case Success =>
task match {
case rt: ResultTask[_, _] =>
...
case smt: ShuffleMapTask =>
...
}
case Resubmitted =>
...
case TaskResultLost =>
...
case other =>
...
}
}
遍历各种情况,分别处理。
RDD 的计算
RDD 的计算是在 task 中完成的,根据窄依赖和宽依赖,又分为 ResultTask 和 ShuffleMapTask,分别看一下具体执行过程:
// ResultTask
override def runTask(context: TaskContext): U = {
// Deserialize the RDD and the func using the broadcast variables.
val ser = SparkEnv.get.closureSerializer.newInstance()
val (rdd, func) = ser.deserialize[(RDD[T], (TaskContext, Iterator[T]) => U)](
ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader)
func(context, rdd.iterator(partition, context))
}
override def runTask(context: TaskContext): MapStatus = {
// Deserialize the RDD using the broadcast variable.
val ser = SparkEnv.get.closureSerializer.newInstance()
val (rdd, dep) = ser.deserialize[(RDD[_], ShuffleDependency[_, _, _])](
ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader)
var writer: ShuffleWriter[Any, Any] = null
try {
val manager = SparkEnv.get.shuffleManager
writer = manager.getWriter[Any, Any](dep.shuffleHandle, partitionId, context)
writer.write(rdd.iterator(partition, context).asInstanceOf[Iterator[_ <: Product2[Any, Any]]])
writer.stop(success = true).get
} catch {
...
}
}
ResultTask 和 ShuffleMapTask 都会调用 SparkEnv.get.closureSerializer 对 taskBinary 进行反序列化操作,也都调用了 RDD.iterator 来计算和转换 RDD。不同之处在于, ResultTask 之后调用 func() 计算结果,而 ShuffleMapTask 把结果存入 blockManager 中用来 Shuffle。
至此,大致分析了 DAGScheduler 的执行过程。下一篇,我们再讲 TaskScheduler。
Spark Scheduler 模块(上)的更多相关文章
- Spark Scheduler模块源码分析之TaskScheduler和SchedulerBackend
本文是Scheduler模块源码分析的第二篇,第一篇Spark Scheduler模块源码分析之DAGScheduler主要分析了DAGScheduler.本文接下来结合Spark-1.6.0的源码继 ...
- Spark Scheduler模块源码分析之DAGScheduler
本文主要结合Spark-1.6.0的源码,对Spark中任务调度模块的执行过程进行分析.Spark Application在遇到Action操作时才会真正的提交任务并进行计算.这时Spark会根据Ac ...
- Spark(五十二):Spark Scheduler模块之DAGScheduler流程
导入 从一个Job运行过程中来看DAGScheduler是运行在Driver端的,其工作流程如下图: 图中涉及到的词汇概念: 1. RDD——Resillient Distributed Datase ...
- Spark Scheduler 模块(下)
Scheduler 模块中最重要的两个类是 DAGScheduler 和 TaskScheduler.上篇讲了 DAGScheduler,这篇讲 TaskScheduler. TaskSchedule ...
- Spark Deploy 模块
Spark Scheduler 模块的文章中,介绍到 Spark 将底层的资源管理和上层的任务调度分离开来,一般而言,底层的资源管理会使用第三方的平台,如 YARN 和 Mesos.为了方便用户测试和 ...
- 【转】Spark源码分析之-scheduler模块
原文地址:http://jerryshao.me/architecture/2013/04/21/Spark%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B- ...
- Spark Scheduler内部原理剖析
文章正文 通过文章“Spark 核心概念RDD”我们知道,Spark的核心是根据RDD来实现的,Spark Scheduler则为Spark核心实现的重要一环,其作用就是任务调度.Spark的任务调度 ...
- Eclipse提交代码到Spark集群上运行
Spark集群master节点: 192.168.168.200 Eclipse运行windows主机: 192.168.168.100 场景: 为了测试在Eclipse上开发的代码在Spa ...
- [Spark Core] 在 Spark 集群上运行程序
0. 说明 将 IDEA 下的项目导出为 Jar 包,部署到 Spark 集群上运行. 1. 打包程序 1.0 前提 搭建好 Spark 集群,完成代码的编写. 1.1 修改代码 [添加内容,判断参数 ...
随机推荐
- input、raw_input区别,运算符,运算优先级,多变赋值方式
目录 1. Python2中的input.raw_input赋值方式和Python3中的input赋值方式的差别 2. 运算符 3. python运算符优先级 4. 格式化输出 5. 链式赋值 6. ...
- Android游戏开发学习(5)--实现Button悬浮于与SurfaceView之上
原文:http://daikainan.iteye.com/blog/1407355 实现Button悬浮于与SurfaceView之上实现 先看效果: 注意:你实现的SurfaceView和andr ...
- 使用display inline-block 布局时,出现的间距问题的解决办法和相关说明
在CSS中,块级对象元素会单独占一行显示,多个block元素会各自新起一行.而内联对象元素前后不会产生换行,一系列inline元素都在一行内显示,直到该行排满. 使用 display inline-b ...
- Nodejs 开发 随手记
console.log(Object.prototype.toString.call(Now.toString())); //类型判断
- 「CF1023F」Mobile Phone Network
「CF1023F」Mobile Phone Network 传送门 直接钦定那 \(k\) 条边在最小生成树中,然后把最小生成树树剖一下. 每条其它边的效果就是把该边端点路径上的边的权对该边边权取 \ ...
- ubuntu 14 双击会自动删除文本
可能是ibus输入法引起,解决方法: Step1打开ibus首选项 $ibus-setup Step2 取消"Embed preedit text in application window ...
- win7系统实现内外网同时连接图文教程
解决方案:修改路由表 在工作中,经常会遇到切换内外网的网络情况,通常情况下都是断开/连接网络,很麻烦.我们可以使用route命令来解决此类问题,route add.route delete.route ...
- win32下的命令行集合 (最优秀的工具)
HIDECMD.rar下载:以隐藏窗口的方式运行批处理. curl.exe 7.12.2 文件传输 593,670 curl是一个利用URL语法在命令行方式下工作的的文件传输工具 E6ED60CDA8 ...
- 初探网络流:dinic/EK算法学习笔记
前记 这些是初一暑假的事: "都快初二了,连网络流都不会,你好菜啊!!!" from 某机房大佬 to 蒟蒻我. flag:--NOIP后要学网络流 咕咕咕------------ ...
- 网络编程之TCP三次握手,四次断开
目录 TCP三次握手 1:上图的名词解释 2:TCP三次握手过程 3:为什么不能改成两次握手? TCP三次握手 1:上图的名词解释 SYN:同步序号.它表示建立连接.TCP规定SYN=1时不能携带数据 ...