【Spark架构】作业执行原理

一 概述

相关术语

  • 作业(Job):RDD中由行动操作所生成的一个或者多个调度阶段
  • 调度阶段(Stage):每个作业会因为RDD之间的依赖关系拆分成多组任务集合,称为调度阶段,也叫做任务集合(TaskSet)。调度阶段的划分是由DAGScheduler俩划分的,调度阶段由ShuffleMapStage和ResultStage两种
  • 任务(Task):分发到Executor的工作任务,是Spark实际执行应用的最小单元。在Spark中主要有两种不同类型的Task,分别是ShuffleMapTask和ResultTask
  • DAGScheduler:DAGScheduler是面向调度阶段的任务调度器,负责接收Spark应用提交的作业,根据RDD的依赖关系划分调度阶段,并提交调度阶段给TaskScheduler
  • TaskScheduler:TaskScheduler是面向任务的调度器,它接收DAGScheduler提交过来的调度阶段,然后把任务分发到Worker阶段上运行,由Worker节点上的Executor进程来运行任务。

整体流程

  1. Spark应用程序进行各种转换操作,通过行动操作触发作业的运行。提交之后根据RDD的依赖关系构建DAG图,DAG图提交给DAGScheduler进行解析。
  2. DAGScheduler是面向调度阶段的高层次调度器,DAGScheduler把DAG拆分成相互依赖的调度阶段,拆分调度阶段是以RDD的依赖是否为宽依赖,当遇到宽依赖就划分为新的调度阶段。每个调度阶段包含一个或者多个任务,这些任务形成任务集合,提交给底层TaskScheduler进行调度运行。另外DAGScheduler还记录了哪些RDD被存入磁盘等物化动作,同时寻求任务的最优调度优化,列如数据本地型。DAGScheduler还监控运行调度过程,如果某个调度阶段运行失败,则需要重新提交该调度阶段。
  3. 每个TaskScheduler只为一个SparkContext实例服务,TaskScheduler接受来自DAGScheduler发送过来的任务集合,TaskScheduler收到任务集合之后就把该任务集合以任务的形式一个一个方法到Worker节点中运行,如果某个任务运行失败,TaskScheduler要负责重试。如果TaskScheduler发现某个任务一直未运行完成,就可能启动同样的任务运行同样的一个任务,哪个任务先运行完成就用哪个任务的结果。
  4. Worke中的Executor收到TaskScheduler发送过来的任务之后,以多线程的方式进行运行,每一个线程负责一个任务。任务结束之后要返回结果给TaskScheduler,不同的类型任务返回的结果不同。ShuffleMapTask返回的是一个MapStatus对象,而不是结果本身;ResultTask根据结果大小的不同,返回的方式不同。

二 提交作业

RDD的源码的行动操作会触发SparkContext的runJob方法来提交作业,SparkContext的runJob方法经过几次调用后,进入DAGScheduler的runJob方法。

在DAGScheduler类内部会进行一列的方法调用,首先是在runJob方法里,调用submitJob方法来继续提交作业,这里会发生阻塞,知道返回作业完成或失败的结果;然后在submitJob方法里,创建了一个JobWaiter对象,并借助内部消息处理进行把这个对象发送给DAGScheduler的内嵌类DAGSchedulerEventProcessLoop进行处理;最后在DAGSchedulerEventProcessLoop消息接收方法OnReceive中,接收到JobSubmitted样例类完成模式匹配后,继续调用DAGScheduler的handleJobSubmitted方法来提交作业,在该方法中进行划分阶段。

三 划分调度阶段

Spark调度阶段的划分是由DAGScheduler实现的,DAGScheduler会从最后一个RDD出发使用广度优先遍历整个依赖树,从而划分调度阶段,调度阶段的划分是以是否为宽依赖进行的,即当某个RDD的操作是Shuffle时,以该Shuffle操作为界限划分成前后两个调度阶段。

代码实现是在DAGScheduler的handleJobSubmitted方法中根据最后一个RDD生成ReusltStage开始的,具体方法从finalRDD使用getParentsStages找出其依赖的祖先RDD是否存在Shuffle操作,若无shuffle操作,则本次作业只有一个ResultStage,该ResultStage不存在父调度阶段;若存在shuffle操作,本次作业存在一个ResultStage和至少一个ShuffleMapStage,该ResultStage存在父调度阶段。

举例说明 

  1. 在SparkContext中提交运行时,会调用DAGScheduler的handleJobSubmitted进行处理,在该方法中会先找到最后一个RDD(即rddG),并调用getParentStages方法。
  2. 在getParentStages方法判断rddG的依赖RDD树中是否存在shuffle操作,在该例子中发现join操作为shuffle操作,则获取该操作的RDD为rddB和rddF
  3. 使用getAncestorShuffleDependencies方法从rddB向前遍历,发现该依赖分支上没有其他的宽依赖,调用newOrUsedShuffleStage方法生成调度阶段ShuffleMapStage0
  4. 使用getAncestorShuffleDependencies方法从rddF向前遍历,发现groupByKey宽依赖操作,以此为分界划分rddC和rddD为ShuffleMapStage1, rddE和rddF为ShuffleMapStage2
  5. 最后生成rddG的ResultStage3。

例子

四 提交调度阶段

  1. 在handleJobSubmitted方法中获取了该例子的最后一个调度阶段ReusltStage3,通过submitStage方法提交该调度阶段。
  2. 在submitStage方法中,先创建作业实例,然后判断该调度阶段是否存在父调度阶段,由于ReusltStage3有两个父调度阶段ShuffleMapStage0和ShuffleMapStage2,所以不能立即提交调度阶段运行,把ReusltStage3放入到等待队列中等待waitingStages中。
  3. 递归调用submitStage方法可以知道ShuffleMapStage0不存在父调度阶段,而ShuffleMapStage2存在父调度阶段ShuffleMapStage1,这样ShuffleMapStage2加入到等待执行调度阶段列表waitingStages中,而ShuffleMapStage0和ShuffleMapStage1两个调度阶段作为第一次调度使用submitMissingTasks方法运行。
  4. Executor任务执行完成时发送消息,DAGScheduler等调度器更新状态时,检查调度阶段运行情况,如果存在执行失败的任务,则重新提交调度阶段;如果所有任务完成,则继续提交调度阶段运行。由于ReusltStage3的父调度阶段没有全部完成,第二次调度阶段只提交ShuffleMapStage2运行。
  5. 当ShuffleMapStage2运行完毕之后,此时ResultStage3的父调度阶段全部完成,提交该调度运行完成。
     

在这里插入图片描述

五 提交任务

  1. 在提交stage中,第一次调用的是ShuffleMapStage0【ShuffleMapStage0会拆分成ShuffleMapStage(0,0), ShuffleMapStage(0,1),同理ShuffleMapStage1也会拆分出两个任务】和ShuffleMapStage1,假设每个stage都只有两个partition,ShuffleMapStage0是TaskSet0,ShuffleMapStage1是TaskSet1,每个TaskSet都有两个任务在执行。
  2. TaskScheduler收到发送过来的任务集TaskSet0和TaskSet1,在submitTasks方法中分别构建TaskSetManager0和TaskSetManager1,并把它们两放到系统的调度池,根据系统设置的调度算法进行调度(FIFO或者FAIR)
  3. 在TaskSchedulerImpl的resourceOffers方法中按照就近原则进行资源分配,每个任务均分配运行代码,数据分片,处理资源。使用CoarseGrainedSchedulerBackend的launchTasks方法把任务发送到Worker节点上的CoarseGrainedExecutorBackend调用其Executor来执行任务
  4. ShuffleMapStage0,ShuffleMapStage1执行完毕,ShuffleMapStage2,ResultStage3会执行1-3步骤,但是ReduceStage3生成的是ResultTask

在这里插入图片描述

六 执行任务

当CoarseGrainedExecutorBackend接收到LaunchTask消息时,会调用Executor的launchTask方法进行处理。在Executor的launchTask方法中,初始化一个TaskRunner来封装任务,它用于管理任务运行时的细节,再把TaskRunner对象放入到ThreadPool中去执行。

对于ShuffleMapTask, 它的计算结果会写到BlockManager之中,最终返回给DAGScheduler的是一个MapStatus。该对象管理了ShuffleMapTask的运算结果存储到BlockManager里的相关存储信息,而不是计算结果本身,这些存储信息将会成为下一阶段的任务需要获得的输入数据时的依据。

七 获取执行结果

对于Executor的计算结果,会根据结果的大小有不同的策略

  1. 生成结果大于1GB,直接丢弃,可以通过spark.driver.maxResultSize来配置
  2. 生成结果大小在[128MB-200kB, 1GB], 会把结果以taskId为编号存入到BlockManager中,然后把编号通过Netty发送给DriverEndpoint,插值是Netty传输框架最大值和Netty预留空间的差值
  3. 生成结果大小在[0, 128MB-200KB],通过Netty直接发送到DriverEndpoint。

任务执行完毕,TaskRunner将任务的执行结果发给DriverEndPoint,DriverEndPoint转给TaskShedulerImpl的statusUpdate方法处理,此方法针对不同的任务状态有不同的处理结果。
 

猜你喜欢

转载自blog.csdn.net/hebaojing/article/details/87929757