一个job的生命历程

dagScheduler.runJob //(1)
--> submitJob ( eventProcessLoop.post(JobSubmitted,***) //(2)
--> eventProcessLoop //(3)
--> onReceive(event: DAGSchedulerEvent) //(4)
--> doOnReceive(event: DAGSchedulerEvent) //(5)
--> case JobSubmitted //(6)
--> dagScheduler.handleJobSubmitted //(7)
--> finalStage =createResultStage(finalRDD, func, partitions, jobId, callSite) //(8)
--> job = new ActiveJob(jobId, finalStage, callSite, listener, properties) //(9)
--> jobIdToActiveJob(jobId) = job //(10)
--> activeJobs += job //(11)
--> finalStage.setActiveJob(job) //(12)
--> stageIds = jobIdToStageIds(jobId).toArray //(13)
--> stageInfos = stageIds.flatMap(id => stageIdToStage.get(id).map(_.latestInfo)) //(14)
--> listenerBus.post(SparkListenerJobStart(job.jobId, jobSubmissionTime, stageInfos, properties)) //(15)
--> submitStage(finalStage) //(16)
--> getMissingParentStages(stage).sortBy(_.id) //(17)
--> finalStage = getOrCreateShuffleMapStage(dependency, jobId) //(18)
--> createShuffleMapStage(dep, firstJobId) //(19)
-->stage = new ShuffleMapStage(id, rdd, numTasks, parents, jobId, rdd.creationSite, shuffleDep)
--> job = new ActiveJob(jobId, finalStage, callSite, listener, properties) //(20)
--> submitStage(finalStage) //(21)//划分和提交stage算法精髓
--> submitMissingTasks(stage, jobId.get) //(22)
--> submitWaitingChildStages(stage) //(23)
--> markMapStageJobAsFinished(job, mapOutputTracker.getStatistics(dependency)) //(24)
(1)所有的action算子都会触发一个job的调度,经过多次不同的runjob重载后停在这里调度 submitJob
(2)调用eventProcessLoop方法,并发送 JobSubmitted 消息给DAGSchedulerEventProcessLoop(DAGScheduler的循环响应函数体)
(3)eventProcessLoop = new DAGSchedulerEventProcessLoop(this)
(4)onReceive 函数是接受 DAGSchedulerEventProcessLoop DAG调度程序的事件接受函数
(5)doOnReceive 实际是步骤4的事件处理函数
(6)根据步骤2的发送事件,触发 JobSubmitted 这个事件响应
(7)dagScheduler 的核心入口
(8)使用触发的job的最后一个RDD创建一个 finalstage,并且放入内存缓存中 stageIdToStage
(9)使用 finalStage 创建一个job。这个job最后一个stage就是final stage
(10)(11)(12)(13)(14)(15)把 job 加入各种内存缓存中,其实就是各个数据结构
(16)提交finalStage。总是从最后开始往前推测。
(17)获取当前stage的父stage。stage的划分算法,主要在这里。waitingForVisit = new Stack[RDD[_]]。栈结构,从最后的stage往前的stage 放进栈中,实现先进后出。符合程序调用顺序。
(18)获取最后一个stage,finalstage
(19)生成一个 ShuffleMapStage
(20)利用finalestage 生成一个job
(21)划分和提交stage算法精髓,划分好stage之后全部放在waiting stage 数据结构中
(22)提交所有在 waiting stage 中的stage,从stage0...finalstage
(23)检查等待的阶段,现在有资格重新提交。提交依赖于给定父级阶段的阶段。当父阶段完成时调用成功
(24)所有的stage划分完并提交结束
------------------------------------------------------------------------------
stage划分算法非常重要,精通spark,必须对stage划分算法很清晰,知道自己编写的spark程序被划分为几个job,每个job被划分为几个stage,
每个stage包含了哪些代码,只有知道每个stage包括哪些代码后。在线上,如果发现某个stage执行特别慢,或者某个stage一直报错,才能针对
特定的stage包含的代码排查问题,或性能调优。
stage划分算法总结:
1.从finalstage倒推(通过 栈 数据结构实现)
2.通过宽依赖,进行stage的划分
3.通过递归,优先提交父stage
------------------------------------------------------------------------------
/**
* 获取某个stage的父stage
* 对于一个stage,如果它的最后一个RDD的所有依赖都是窄依赖,将不会创建新的stage
* 如果其RDD会依赖某个RDD,用宽依赖的RDD创建一个新的stage,并立即返回这个stage
* @type {[type]}
*/
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) {
//遍历RDD的依赖,对于每种具有shuffle的操作,如reduceByKey,groupByKey,countByKey,底层对应了3个RDD:
//Map
for (dep <- rdd.dependencies) {
dep match {
//如果是宽依赖
case shufDep: ShuffleDependency[_, _, _] =>
//使用宽依赖的RDD创建一个 ShuffleMapStage,并且将isShuffleMap 设置为true,
//默认最后一个stage不是shuffle不是ShuffleMapStage,但是finalstage之前所有的stage都是ShuffleMapStage
val mapStage = getOrCreateShuffleMapStage(shufDep, stage.firstJobId)
if (!mapStage.isAvailable) {
missing += mapStage
} //如果是窄依赖
case narrowDep: NarrowDependency[_] =>
//将依赖的RDD放入栈中
waitingForVisit.push(narrowDep.rdd)
}
}
}
}
}
//
waitingForVisit.push(stage.rdd)
while (waitingForVisit.nonEmpty) {
//
visit(waitingForVisit.pop())
}
missing.toList
}
taskScheduler
-->taskSchedulerImpl (standalone模式)
    -->SparkDeploySchedulerBackend (负责创建AppClient, 向master注册Application)
在TaskSchedulerImpl中,对一个单独的taskset的任务进行调度.这个类负责追踪每一个taskset,如果task失败的话
会负责重试spark,直到超过重试次数,并且会通知延迟调度,为这个taskSet处理本地化机制.它的主要接口是
resourceOffer,在这个接口中,taskset会希望在一个节点上运行一个任务,并且接受任务的状态改变消息,
来知道它负责的task的状态改变了.
override def submitTasks(taskSet: TaskSet) {
val tasks = taskSet.tasks //获取ttaskSet的task列表
logInfo("Adding task set " + taskSet.id + " with " + tasks.length + " tasks")
this.synchronized {
//每个taskSet都会创建一个manager,用于管理每个taskSet,并设定最大失败次数 maxTaskFailures
val manager = createTaskSetManager(taskSet, maxTaskFailures)
val stage = taskSet.stageId
//尝试连接task,如果task失败,会负责重试spark,直到超过重试次数,并且会通知延迟调度
val stageTaskSets =
taskSetsByStageIdAndAttempt.getOrElseUpdate(stage, new HashMap[Int, TaskSetManager])
//通过 manager 获得活着的taskSet
stageTaskSets(taskSet.stageAttemptId) = manager
val conflictingTaskSet = stageTaskSets.exists { case (_, ts) =>
ts.taskSet != taskSet && !ts.isZombie
}
if (conflictingTaskSet) {
throw new IllegalStateException(s"more than one active taskSet for stage $stage:" +
s" ${stageTaskSets.toSeq.map{_._2.taskSet.id}.mkString(",")}")
}
//利用已选择的调度器schedulableBuilder,把一个taskSet的manager加入调度管理池中
/*
def initialize(backend: SchedulerBackend) {
this.backend = backend
schedulableBuilder = {
schedulingMode match {
case SchedulingMode.FIFO =>
new FIFOSchedulableBuilder(rootPool)
case SchedulingMode.FAIR =>
new FairSchedulableBuilder(rootPool, conf)
case _ =>
throw new IllegalArgumentException(s"Unsupported $SCHEDULER_MODE_PROPERTY: " +
s"$schedulingMode")
}
}
schedulableBuilder.buildPools()
}*/
schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties) if (!isLocal && !hasReceivedTask) {
starvationTimer.scheduleAtFixedRate(new TimerTask() {
override def run() {
if (!hasLaunchedTask) {
logWarning("Initial job has not accepted any resources; " +
"check your cluster UI to ensure that workers are registered " +
"and have sufficient resources")
} else {
this.cancel()
}
}
}, STARVATION_TIMEOUT_MS, STARVATION_TIMEOUT_MS)
}
hasReceivedTask = true
}
/**
* 创建 taskScheduler 的时候,就是为 taskSchedulerImpl 创建一个 SparkDeploySchedulerBackend .
* 它负责创建AppClient,向master注册Application
*/
backend.reviveOffers()
}

一个Spark job的生命历程的更多相关文章

  1. 连载《一个程序猿的生命周期》-《发展篇》 - 3.农民与软件工程师,农业与IT业

    相关文章:随笔<一个程序猿的生命周期>- 逆潮流而动的“叛逆者”        15年前,依稀记得走出大山,进城求学的场景.尽管一路有父亲的陪伴,但是内心仍然畏惧.当父亲转身离去.准备回到 ...

  2. Spark记录(二):Spark程序的生命周期

    本文以Spark执行模式中最常见的集群模式为例,详细的描述一下Spark程序的生命周期(YARN作为集群管理器). 1.集群节点初始化 集群刚初始化的时候,或者之前的Spark任务完成之后,此时集群中 ...

  3. 连载《一个程序猿的生命周期》- 44.感谢,我从事了IT相关的工作

    感谢博客园一直以来的支持,写连载都是在这里首发,相比较CSDN和开源中国气氛要好的多. 节前,想以此篇文章结束<一个程序猿的生命周期>的<生存>篇,对过10的年做一个了断,准备 ...

  4. Spark集群 + Akka + Kafka + Scala 开发(2) : 开发一个Spark应用

    前言 在Spark集群 + Akka + Kafka + Scala 开发(1) : 配置开发环境,我们已经部署好了一个Spark的开发环境. 本文的目标是写一个Spark应用,并可以在集群中测试. ...

  5. 连载《一个程序猿的生命周期》-28、被忽悠来的单身HR(女同志)

    一个程序猿的生命周期 微信平台 口   号:职业交流,职业规划:面对现实,用心去交流.感悟. 公众号:iterlifetime 百木-ITer职业交流奋斗 群:141588103    微   博:h ...

  6. 连载《一个程序猿的生命周期》-6、自学C++,二级考过后,为工作的机会打下了基础

    一个程序猿的生命周期 微信平台 口   号:职业交流,职业规划:面对现实,用心去交流.感悟. 公众号:iterlifetime 百木-ITer职业交流奋斗 群:141588103    微   博:h ...

  7. 阅读<构建之法>第13、14、15、16、17章 与 《一个程序员的生命周期》读后感

    第十三章   软件测试 这一章介绍了很多关于测试的方法,比如说单元测试,代码覆盖率测试,构建验证测试,验收测试等,我有一个很纠结的问题,如果我开发软件,是把这么多测试全做完,还是挑一些测试来进行呢?如 ...

  8. 从源码剖析一个Spark WordCount Job执行的全过程

      原文地址:http://mzorro.me/post/55c85d06e40daa9d022f3cbd   WordCount可以说是分布式数据处理框架的”Hello World”,我们可以以它为 ...

  9. 连载《一个程序猿的生命周期》-《发展篇》 - 7.是什么阻碍了"程序猿"的发展?

    有两件事想记录一下,具有普遍性和代表性."程序猿"加了引号,是泛指一类人,也并非局限于IT行业.       山东子公司的总经理是公司大股东之一,个子不高.有些秃顶.面容显老,但看 ...

随机推荐

  1. html5--移动端视频video的android兼容,去除播放控件、全屏等

    html5 中的video 在手机浏览器中的总结所有页面播放时, 如果选择全屏播放, 播放画面将浮动到屏幕的最上层 IOS 手机   自动播放 播放界面浮动文字 播放时是否自动全屏 能否嵌入在页面中播 ...

  2. mybatis由浅入深day01_4.9删除用户_4.10更新用户

    4.9 删除用户 4.9.1 映射文件 4.9.2 代码: 控制台: 4.10 更新用户 4.10.1 映射文件 4.10.2 代码 控制台:

  3. Swift-基础语法之变量&常量&元组

    使用 let来声明一个常量,用 var来声明一个变量.常量的值在编译时并不要求已知,但是你必须为其赋值一次.这意味着你可以使用常量来给一个值命名,然后一次定义多次使用 myVariable = let ...

  4. python2.0_day19_后台数据库设计思路

    from django.db import models # Create your models here. from django.contrib.auth.models import User ...

  5. Python 爬虫实战

    图片爬虫实战 链接爬虫实战 糗事百科爬虫实战 微信爬虫实战 多线程爬虫实战

  6. Centos下Nagios的安装与配置

    一.Nagios简介 Nagios是一款开源的电脑系统和网络监视工具,能有效监控Windows.Linux和Unix的主机状态,交换机路由器等网络设置,打印机等.在系统或服务状态异常时发出邮件或短信报 ...

  7. C语言各种存储模式的区别?最常用的存储模式有哪些?

    DOS用一种段地址结构来编址计算机的内存,每一个物理内存位置都有一个可通过段地址一偏移量的方式来访问的相关地址.为了支持这种段地址结构,大多数C编译程序都允许你用以下6种存储模式来创建程序: ---- ...

  8. Effective C++ —— 设计与声明(四)

    条款18 : 让接口容易被正确使用,不易被误用 欲开发一个“容易被正确使用,不容易被误用”的接口,首先必须考虑客户可能做出什么样的错误操作.  1. 明智而审慎地导入新类型对预防“接口被误用”有神奇疗 ...

  9. Gamma编码及Delta编码概述

    一.Elias Gamma Coding 即Gamma编码,是一种对正整数进行编码的统一编码,由Peter Elias发明.适用于预先无法获知最大编码整数的情况,而且小整数出现频率高,大整数出现频率低 ...

  10. 【Linux】 ftp 主动被动模式

    LNMP 搭建得服务器,在使用ftp时候,报如下错误: 经查,是ftp 主动模式被动模式问题 工具:  Xftp5   ,把被动模式勾 取消 (其他客户端可以网上查一下 相应的 被动模式转主动模式设置 ...