Spark源码系列(四)图解作业生命周期
这一章我们探索了Spark作业的运行过程,但是没把整个过程描绘出来,好,跟着我走吧,let you know!

我们先回顾一下这个图,Driver Program是我们写的那个程序,它的核心是SparkContext,回想一下,从api的使用角度,RDD都必须通过它来获得。
下面讲一讲它所不为认知的一面,它和其它组件是如何交互的。
Driver向Master注册Application过程
SparkContext实例化之后,在内部实例化两个很重要的类,DAGScheduler和TaskScheduler。
在standalone的模式下,TaskScheduler的实现类是TaskSchedulerImpl,在初始化它的时候SparkContext会传入一个SparkDeploySchedulerBackend。
在SparkDeploySchedulerBackend的start方法里面启动了一个AppClient。
val command = Command("org.apache.spark.executor.CoarseGrainedExecutorBackend", args, sc.executorEnvs, classPathEntries, libraryPathEntries, extraJavaOpts)
val sparkHome = sc.getSparkHome()
val appDesc = new ApplicationDescription(sc.appName, maxCores, sc.executorMemory, command,
sparkHome, sc.ui.appUIAddress, sc.eventLogger.map(_.logDir))
client = new AppClient(sc.env.actorSystem, masters, appDesc, this, conf)
client.start()
maxCores是由参数spark.cores.max来指定的,executorMemoy是由spark.executor.memory指定的。
AppClient启动之后就会去向Master注册Applicatoin了,后面的过程我用图来表达。

上面的图中涉及到了三方通信,具体的过程如下:
1、Driver通过AppClient向Master发送了RegisterApplication消息来注册Application,Master收到消息之后会发送RegisteredApplication通知Driver注册成功,Driver的接收类还是AppClient。
2、Master接受到RegisterApplication之后会触发调度过程,在资源足够的情况下会向Woker和Driver分别发送LaunchExecutor、ExecutorAdded消息。
3、Worker接收到LaunchExecutor消息之后,会执行消息中携带的命令,执行CoarseGrainedExecutorBackend类(图中仅以它继承的接口ExecutorBackend代替),执行完毕之后会发送ExecutorStateChanged消息给Master。
4、Master接收ExecutorStateChanged之后,立即发送ExecutorUpdated消息通知Driver。
5、Driver中的AppClient接收到Master发过来的ExecutorAdded和ExecutorUpdated后进行相应的处理。
6、启动之后的CoarseGrainedExecutorBackend会向Driver发送RegisterExecutor消息。
7、Driver中的SparkDeploySchedulerBackend(具体代码在CoarseGrainedSchedulerBackend里面)接收到RegisterExecutor消息,回复注册成功的消息RegisteredExecutor给ExecutorBackend,并且立马准备给它发送任务。
8、CoarseGrainedExecutorBackend接收到RegisteredExecutor消息之后,实例化一个Executor等待任务的到来。
资源的调度
好,在我们讲完了整个注册Application的通信过程之后,其中一个比较重要的地方是它的调度这块,它是怎么调度的?这也是我在前面为什么那么强调maxCores和executorMemoy的原因。
细心的读者如果看了第一章《spark-submit提交作业过程》的就知道,其实我已经讲过调度了,因为当时不知道这个app是啥。但是现在我们知道app是啥了。代码我不就贴了,总结一下吧。
1、先调度Driver,再调度Application。
2、它的调度Application的方式是先进先出,所以就不要奇怪为什么你的App总得不到调度了,就像去北京的医院看病,去晚了号就没了,是一个道理。
3、Executor的分配方式有两种,一种是倾向于把任务分散在多个节点上,一种是在尽量少的节点上运行,由参数spark.deploy.spreadOut参数来决定的,默认是true,把任务分散到多个节点上。
遍历所有等待的Application,给它分配Executor运行的Worker,默认分配方式如下:
1、先从workers当中选出内存大于executorMemoy的worker,并且按照空闲cpu数从大到小的顺序来排序。
2、遍历worker,从可用的worker分配需要的cpu数,每个worker提供一个cpu核心,直到cpu数不足或者maxCores分配完毕。
3、给选出来的worker发送任务,让它们启动Executor,每个Executor占用的内存是我们设定的executorMemoy。
资源调度的过程大体是这样的,说到这里有些童鞋在有点儿疑惑了,那我们任务调度里面FIFO/FAIR调度是在哪里用的?任务调度器调度的不是Application,而是你的代码里面被解析出来的所有Task,这在上一章当中有提到。
基于这个原因,在共用SparkContext的情况下,比如Shark、JobServer什么的,任务调度器的作用才会明显。
Driver向Executor发布Task过程
下面我们讲一讲Driver向Executor发布Task过程,这在上一章是讲过的,现在把图给大家放出来了。

1、Driver程序的代码运行到action操作,触发了SparkContext的runJob方法。
2、SparkContext比较懒,转手就交给DAGScheduler。
3、DAGScheduler把Job划分stage,然后把stage转化为相应的Tasks,把Tasks交给TaskScheduler。
4、通过TaskScheduler把Tasks添加到任务队列当中,转手就交给SchedulerBackend了。
5、调度器给Task分配执行Executor,ExecutorBackend负责执行Task了。
补充:ExecutorBackend执行Task,是通过它内部的Executor来执行的,Executor内部有个线程池,new了一个TaskRunner交给线程池了。
Task状态更新
Task执行是通过TaskRunner来运行,它需要通过ExecutorBackend和Driver通信,通信消息是StatusUpdate:
1、Task运行之前,告诉Driver当前Task的状态为TaskState.RUNNING。
2、Task运行之后,告诉Driver当前Task的状态为TaskState.FINISHED,并返回计算结果。
3、如果Task运行过程中发生错误,告诉Driver当前Task的状态为TaskState.FAILED,并返回错误原因。
4、如果Task在中途被Kill掉了,告诉Driver当前Task的状态为TaskState.FAILED。
下面讲的是运行成功的状态,具体过程以文字为主。

1、Task运行结束之后,调用ExecutorBackend的statusUpdate方法,把结果返回。结果超过10M,就把结果保存在blockManager处,返回blockId,需要的时候通过blockId到blockManager认领。
2、ExecutorBackend直接向Driver发送StatusUpdate返回Task的信息。
3、Driver(这里具体指的是SchedulerBackend)接收到StatusUpdate消息之后,调用TaskScheduler的statusUpdate方法,然后准备给ExecutorBackend发送下一批Task。
4、TaskScheduler通过TaskId找到管理这个Task的TaskSetManager(负责管理一批Task的类),从TaskSetManager里面删掉这个Task,并把Task插入到TaskResultGetter(负责获取Task结果的类)的成功队列里。
5、TaskResultGetter获取到结果之后,调用TaskScheduler的handleSuccessfulTask方法把结果返回。
6、TaskScheduler调用TaskSetManager的handleSuccessfulTask方法,处理成功的Task。
7、TaskSetManager调用DAGScheduler的taskEnded方法,告诉DAGScheduler这个Task运行结束了,如果这个时候Task全部成功了,就会结束TaskSetManager。
8、DAGScheduler在taskEnded方法里触发CompletionEvent事件,CompletionEvent分ResultTask和ShuffleMapTask来处理。
1)ResultTask:job的numFinished加1,如果numFinished等于它的分片数,则表示任务该Stage结束,标记这个Stage为结束,最后调用JobListener(具体实现在JobWaiter)的taskSucceeded方法,把结果交给resultHandler(经过包装的自己写的那个匿名函数)处理,如果完成的Task数量等于总任务数,任务退出。
2)ShuffleMapTask:
(1)调用Stage的addOutputLoc方法,把结果添加到Stage的outputLocs列表里。
(2)如果该Stage没有等待的Task了,就标记该Stage为结束。
(3)把Stage的outputLocs注册到MapOutputTracker里面,留个下一个Stage用。
(4)如果Stage的outputLocs为空,表示它的计算失败,重新提交Stage。
(5)找出下一个在等待并且没有父亲的Stage提交。
岑玉海
转载请注明出处,谢谢!
Spark源码系列(四)图解作业生命周期的更多相关文章
- Spark源码系列(六)Shuffle的过程解析
Spark大会上,所有的演讲嘉宾都认为shuffle是最影响性能的地方,但是又无可奈何.之前去百度面试hadoop的时候,也被问到了这个问题,直接回答了不知道. 这篇文章主要是沿着下面几个问题来开展: ...
- Spark源码系列:RDD repartition、coalesce 对比
在上一篇文章中 Spark源码系列:DataFrame repartition.coalesce 对比 对DataFrame的repartition.coalesce进行了对比,在这篇文章中,将会对R ...
- Spark 源码系列(六)Shuffle 的过程解析
Spark 大会上,所有的演讲嘉宾都认为 shuffle 是最影响性能的地方,但是又无可奈何.之前去百度面试 hadoop 的时候,也被问到了这个问题,直接回答了不知道. 这篇文章主要是沿着下面几个问 ...
- 你还不知道Vue的生命周期吗?带你从Vue源码了解Vue2.x的生命周期(初始化阶段)
作者:小土豆biubiubiu 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/58c61b4361ff4b005d9e8 ...
- Spark源码系列(三)作业运行过程
作业执行 上一章讲了RDD的转换,但是没讲作业的运行,它和Driver Program的关系是啥,和RDD的关系是啥? 官方给的例子里面,一执行collect方法就能出结果,那我们就从collect开 ...
- Spring源码系列(四)--spring-aop是如何设计的
简介 spring-aop 用于生成动态代理类(底层是使用 JDK 动态代理或 cglib 来生成代理类),搭配 spring-bean 一起使用,可以使 AOP 更加解耦.方便.在实际项目中,spr ...
- Spark源码系列(一)spark-submit提交作业过程
前言 折腾了很久,终于开始学习Spark的源码了,第一篇我打算讲一下Spark作业的提交过程. 这个是Spark的App运行图,它通过一个Driver来和集群通信,集群负责作业的分配.今天我要讲的是如 ...
- Spark Streaming源码解读之生成全生命周期彻底研究与思考
本期内容 : DStream与RDD关系彻底研究 Streaming中RDD的生成彻底研究 问题的提出 : 1. RDD是怎么生成的,依靠什么生成 2.执行时是否与Spark Core上的RDD执行有 ...
- 框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)
一.为什么要提供配置的方法 经过前面的手写Spring IOC.手写Spring DI.手写Spring AOP,我们知道要创建一个bean对象,需要用户先定义好bean,然后注册到bean工厂才能创 ...
- Spark源码系列(五)分布式缓存
这一章想讲一下Spark的缓存是如何实现的.这个persist方法是在RDD里面的,所以我们直接打开RDD这个类. def persist(newLevel: StorageLevel): this. ...
随机推荐
- 创建Fragment
你可以认为fragment是Activity中模块化的部分.Fragment有它自己的生命周期,接收它自己的输入事件,并且你可以在Activity运行的时候添加或移除它(有点像可以被重用在不同Acti ...
- [JS12] 统计访问次数
<html> <head> <meta http-equiv="Content-Type" content="text/html; char ...
- web应用性能测试-Tomcat 7 连接数和线程数配置
转自:http://www.jianshu.com/p/8445645b3aff 引言 这段时间折腾了哈java web应用的压力测试,部署容器是tomcat 7.期间学到了蛮多散碎的知识点,及时梳理 ...
- GCD使用dispatch_group_notify、dispatch_group_enter、dispatch_group_leave处理多线程同步操作
一.简介 dispatch_group_enter:通知group,下面的任务马上要放到group中执行了. dispatch_group_leave:通知group,任务完成了,该任务要从group ...
- [jQuery学习系列五 ]5-Jquery学习五-表单验证
前言最近总是有一个感觉,虽然这些东西都自己学习并一个案例一个案例的去验证过了.但是总觉得不写成博客记录下来这些都不是自己的东西(心理作用,哈哈).所以每当学习或者复习相关的知识我都喜欢记录下来,下面开 ...
- 大家一起写mvc(三)_结束
上一篇介绍到要写mvc的所用的核心技术,这一篇我们就开始真正的开始写mvc,其实就是把昨天写过的代码进行一些组装就可以了. 我们用eclipse新建一个web项目.然后web.xml如下 <?x ...
- php对csv文件的读取,写入,输出下载操作
在实际工作中,很多时候需要把网站上的一些数据下载到CSV文件里,方便以后查看. 亦或者是用CSV进行一些批量的上传工作. 这个时候我们就需要对CSV进行读写操作. 1.CSV的读取操作 <?ph ...
- [mongodb-10gen]ubuntu下安装方法
由于自己老是不死心,所以还是继续在UBUNTU下开始我的GOLANG,今天发现服务源很快一下子就把GO的源代码从GoogleProject上给hg回来了,所以今天才查了一下在UBUNTU下安装Mong ...
- svchost占用内存达1-2G的问题
win7 64位,前一段时间老是如此,很烦,重装,还是有这个问题. 服务中禁用superfect服务,关闭后继续出现1G以上的内存占用,下载svchost viewer检查,发现: 关闭windows ...
- Hadoop-2.6.0 + Zookeeper-3.4.6 + HBase-0.98.9-hadoop2环境搭建示例
1 基本信息 1.1 软件信息 hadoop-2.6.0 zookeeper-3.4.6 hbase-0.98.9-hadoop2 (以下示例中使用的操作系统是Centos 6.5,请将 ...