Spark Scheduler模块源码分析之DAGScheduler
来源:互联网 发布:Mac大括号怎么打 编辑:程序博客网 时间:2024/05/05 07:07
在任务调度模块中最重要的三个类是:
1. org.apache.spark.scheduler.DAGScheduler
2. org.apache.spark.scheduler.SchedulerBackend
3. org.apache.spark.scheduler.TaskScheduler
这里面SchedulerBackend主要起到的作用是为Task分配计算资源。
由于TaskScheduler与SchedulerBackend结合比较紧密,并且从生成来看都是在同一个方法生成,所以接下来分成两篇博客对这三个主要的类进行分析,本文分析DAGScheduler的执行过程。有关SchedulerBackend和TaskScheduler的分析,可以访问Spark Scheduler模块源码分析之TaskScheduler和SchedulerBackend。
一、DAGScheduler的构建
Spark在构造SparkContext时就会生成DAGScheduler的实例。
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
在生成_dagScheduler之前,已经生成了_schedulerBackend和_taskScheduler对象。这两个对象会在接下来第二和第三部分中介绍。之所以taskScheduler对象在dagScheduler对象构造之前先生成,是由于在生成DAGScheduler的构造方法中会从传入的SparkContext中获取到taskScheduler对象def this(sc: SparkContext) = this(sc, sc.taskScheduler)
。
看一下DAGScheduler对象的主构造方法,
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
其中有关LiveListenerBus会在Spark-1.6.0之Application运行信息记录器JobProgressListener中有具体介绍。MapOutputTrackerMaster,BlockManagerMaster后续也会写博客进行分析。
DAGScheduler的数据结构
在DAGScheduler的源代码中,定义了很多变量,在刚构造出来时,仅仅只是初始化这些变量,具体使用是在后面Job提交的过程中了。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
DAGScheduler构造完成,并初始化一个eventProcessLoop实例后,会调用其eventProcessLoop.start()
方法,启动一个多线程,然后把各种event都提交到eventProcessLoop中。这个eventProcessLoop比较重要,在后面也会提到。
二、Job的提交
一个Job实际上是从RDD调用一个Action操作开始的,该Action操作最终会进入到org.apache.spark.SparkContext.runJob()
方法中,在SparkContext中有多个重载的runJob方法,最终入口是下面这个:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
这里调用dagScheduler.runJob()
方法后,正式进入之前构造的DAGScheduler对象中。在这个方法中,后续一系列的过程以此为:
1. DAGScheduler#runJob
执行过程中各变量的内容如下图所示
调用DAGScheduler.submitJob方法后会得到一个JobWaiter实例来监听Job的执行情况。针对Job的Succeeded状态和Failed状态,在接下来代码中都有不同的处理方式。
2. DAGScheduler#submitJob
进入submitJob方法,首先会去检查rdd的分区信息,在确保rdd分区信息正确的情况下,给当前job生成一个jobId,nexJobId在刚构造出来时是从0开始编号的,在同一个SparkContext中,jobId会逐渐顺延。然后构造出一个JobWaiter对象返回给上一级调用函数。通过上面提到的eventProcessLoop提交该任务,最终会调用到DAGScheduler.handleJobSubmitted
来处理这次提交的Job。handleJobSubmitted在下面的Stage划分部分会有提到。
3. DAGSchedulerEventProcessLoop#post
在前面的方法中,调用post方法传入的是一个JobSubmitted实例。DAGSchedulerEventProcessLoop类继承自EventLoop类,其中的post方法也是在EventLoop中定义的。在EventLoop中维持了一个LinkedBlockingDeque类型的事件队列,将该Job提交事件存入该队列后,事件线程会从队列中取出事件并进行处理。
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
4、EventLoop#run
该方法从eventQueue队列中顺序取出event,调用onReceive方法处理事件
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
5、DAGSchedulerEventProcessLoop#onReceive
在onReceive方法中,进一步调用doOnReceive方法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
6、DAGSchedulerEventProcessLoop#doOnReceive
在该方法中,根据事件类别分别匹配不同的方法进一步处理。本次传入的是JobSubmitted方法,那么进一步调用的方法是DAGScheduler.handleJobSubmitted。这部分的逻辑,以及还可以处理的其他事件,都在下面的源代码中。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
7、DAGScheduler#handleJobSubmitted
当Job提交后,JobSubmitted事件会被eventProcessLoop捕获到,然后进入本方法中。开始处理Job,并执行Stage的划分。这一部分会衔接下一节,所以这个方法的源码以及Stage如何划分会在下一节中详细描述。
三、Stage的划分
Stage的划分过程中,会涉及到宽依赖和窄依赖的概念,宽依赖是Stage的分界线,连续的窄依赖都属于同一Stage。
比如上图中,在RDD G处调用了Action操作,在划分Stage时,会从G开始逆向分析,G依赖于B和F,其中对B是窄依赖,对F是宽依赖,所以F和G不能算在同一个Stage中,即在F和G之间会有一个Stage分界线。上图中还有一处宽依赖在A和B之间,所以这里还会分出一个Stage。最终形成了3个Stage,由于Stage1和Stage2是相互独立的,所以可以并发执行,等Stage1和Stage2准备就绪后,Stage3才能开始执行。
Stage有两个类型,最后的Stage为ResultStage类型,除此之外的Stage都是ShuffleMapStage类型。
1、DAGScheduler#handleJobSubmitted
这个方法的具体代码如下所示,前面提到了Stage的划分是从最后一个Stage开始逆推的,每遇到一个宽依赖处,就分裂成另外一个Stage,依此类推直到Stage划分完毕为止。并且,只有最后一个Stage的类型是ResultStage类型。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
2、DAGScheduler#newResultStage
在这个方法中,会根据最后调用Action的那个RDD,以及方法调用过程callSite,生成的jobId,partitions等信息生成最后那个Stage。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
3、DAGScheduler#getParentStagesAndId
这个方法主要是为当前的RDD向前探索,找到宽依赖处划分出parentStage,并为当前RDD所属Stage生成一个stageId。在这个方法中,getParentStages的调用链最终递归调用到了这个方法,所以,最后一个Stage的stageId最大,越往前的stageId就越小,stageId小的Stage先执行。
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
4、DAGScheduler#getParentStages
从当前rdd开始往前探索父rdd,在每一个宽依赖处生成一个parentStage,而窄依赖的rdd,继续压入栈中,等待下一轮分析窄依赖父rdd的父rdd,一直找到宽依赖生成新的stage,或者直到第一个rdd为止。同时,使用一个HashSet来保存访问过的rdd,后面分析时遇到重复依赖时也能保证每个rdd只被分析了一次。一个Job中,除了最后一个Stage是ResultStage类型之外,他的Stage都是ShuffleMapStage结构。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
5、DAGScheduler.newOrUsedShuffleStage
这里会为当前Shuffle生成一个ShuffleMapStage,并且会与MapOutputTracker打交道,记录本次Shuffle的一些信息。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
6、DAGScheduler#getShuffleMapStage
为当前宽依赖的Map端生成一个新的ShuffleMapStage类型的Stage。同时也为当前Shuffle的父Shuffle生成一个Stage。通过DAGScheduler.getAncestorShuffleDependencies
获取当前Shuffle的父Shuffle,这个方法的逻辑和上面的DAGScheduler.getParentStages
获取当前Stage的父Stage类似。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
7、DAGScheduler#newShuffleMapStage
这个和2类似,不同的是2是生成最终的ResultStage,而这里是生成ShuffleMapStage,不过这两者都会调用方法3,最终形成了一个递归调用。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
四、Stage的提交
这部分主要梳理Stage生成后如何提交,任务的提交和生成入口在前面DAGScheduler#handleJobSubmitted方法中。
1、DAGScheduler#handleJobSubmitted
这个方法的代码可以到第三节中查看。生成了finalStage后,就会为该Job生成一个ActiveJob对象了,并准备计算这个finalStage。
ActiveJob对象中的信息比较少,可以看其类定义
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
在DAGScheduler.handleJobSubmitted
方法的最后,调用了DAGScheduler.submitStage
方法,在提交finalSate的前面,会通过listenerBus的post方法,把Job开始的事件提交到Listener中。
2、DAGScheduler#submitStage
提交Job的提交,是从最后那个Stage开始的。如果当前stage已经被提交过,处于waiting或者waiting状态,或者当前stage已经处于failed状态则不作任何处理,否则继续提交该stage。
在提交时,需要当前Stage需要满足依赖关系,其前置的Parent Stage都运行完成后才能轮得到当前Stage运行。如果还有Parent Stage未运行完成,则优先提交Parent Stage。通过调用方法DAGScheduler.getMissingParentStages
方法获取未执行的Parent Stage。
如果当前Stage满足上述两个条件后,调用DAGScheduler.submitMissingTasks
方法,提交当前Stage。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
2.1 DAGScheduler#getMissingParentStage
这个方法用于获取stage未执行的Parent Stage。在上面方法中,获取到Parent Stage后,递归调用上面那个方法按照StageId小的先提交的原则,这个方法的逻辑和DAGScheduler#getParentStages方法类似,这里不再分析了。总之就是根据当前Stage,递归调用其中的visit方法,依次对每一个Stage追溯其未运行的Parent Stage。
3、DAGScheduler.submitMissingTasks
当Stege的Parent Stage都运行完毕,才能调用这个方法真正的提交当前Stage中包含的Task。这个方法涉及到了Task,会在Spark Scheduler模块源码分析之TaskScheduler和SchedulerBackend中进一步分析。
到这里,本文主要分析了Scheduler模块中DAGScheduler的作用,构成,以及Stage划分和Stage最终的提交过程,仔细观察这一部分的主要代码中,在多处都会看到listenerBus.post
方法的调用,针对不同的Stage事件,会将这个事件提交到LiveListenerBus中,将Stage事件相关过程进行记录,并使得Spark其他部分能够及时获取到Stage的最新状态。这一部分可以参考Spark-1.6.0之Application运行信息记录器JobProgressListener。
- Spark Scheduler模块源码分析之DAGScheduler
- Spark Scheduler模块源码分析之DAGScheduler
- Spark Scheduler模块源码分析之DAGScheduler
- Spark源码分析之-scheduler模块
- Spark源码分析之-scheduler模块
- Spark源码分析之-scheduler模块
- Spark源码分析之-scheduler模块
- Spark源码分析之Scheduler模块(TaskScheduler)
- Spark源码分析之-scheduler模块
- Spark源码分析之Scheduler模块(TaskScheduler)
- Spark源码分析之Scheduler模块(TaskScheduler)
- Spark Scheduler模块源码分析之TaskScheduler和SchedulerBackend
- Spark分析之DAGScheduler
- Spark源码分析之DAGScheduler以及stage的划分
- (八)Spark源码理解之DAGScheduler---part1
- (八)Spark源码理解之DAGScheduler---part2
- (八)Spark源码理解之DAGScheduler---part3
- 【Spark】DAGScheduler源码浅析
- 视频带宽计算公式(码流_分辨率_帧率)
- Redis探索之旅(1)- Redis初识
- Java二分法查找实现
- 获取src/main/resources下文件的绝对路径
- 典型数据库架构设计与实践 | 架构师之路
- Spark Scheduler模块源码分析之DAGScheduler
- java自动运行任务job---quartz的运用
- Android性能优化全方面解析
- MYSQL的UPDATE子查询,UPDATE时避免使用子查询
- ECMAScript6(2):解构赋值
- ORACLE(DEDICATED)式(SHARE)的区别
- 文章标题
- php进阶之数据库设计/ 选择合适的表引擎
- clumsy模拟客户端网络差的场景的使用