Spark Core源代码分析: Spark任务运行模型
DAGScheduler
面向stage的调度层,为job生成以stage组成的DAG,提交TaskSet给TaskScheduler运行。
每个Stage内,都是独立的tasks,他们共同运行同一个compute function,享有同样的shuffledependencies。DAG在切分stage的时候是按照出现shuffle为界限的。
private[spark]
class DAGScheduler(
taskScheduler: TaskScheduler,
listenerBus: LiveListenerBus,
mapOutputTracker: MapOutputTrackerMaster,
blockManagerMaster: BlockManagerMaster,
env: SparkEnv)
extends Logging {
// Actor模式收取发来的DAGSchedulerEvent,并进行processEvent
private var eventProcessActor: ActorRef = _ private[scheduler] val nextJobId = new AtomicInteger(0)
private[scheduler] def numTotalJobs: Int = nextJobId.get()
private val nextStageId = new AtomicInteger(0) // 一系列信息维护,非常清晰
private[scheduler] val jobIdToStageIds = new HashMap[Int, HashSet[Int]]
private[scheduler] val stageIdToJobIds = new HashMap[Int, HashSet[Int]]
private[scheduler] val stageIdToStage = new HashMap[Int, Stage]
private[scheduler] val shuffleToMapStage = new HashMap[Int, Stage]
private[scheduler] val jobIdToActiveJob = new HashMap[Int, ActiveJob]
private[scheduler] val resultStageToJob = new HashMap[Stage, ActiveJob]
private[scheduler] val stageToInfos = new HashMap[Stage, StageInfo] // 不同状态stages的维护,非常清晰
// Stages we need to run whose parents aren't done
private[scheduler] val waitingStages = new HashSet[Stage] // Stages we are running right now
private[scheduler] val runningStages = new HashSet[Stage] // Stages that must be resubmitted due to fetch failures
private[scheduler] val failedStages = new HashSet[Stage] // Missing tasks from each stage
private[scheduler] val pendingTasks = new HashMap[Stage, HashSet[Task[_]]] private[scheduler] val activeJobs = new HashSet[ActiveJob] // Contains the locations that each RDD's partitions are cached on
private val cacheLocs = new HashMap[Int, Array[Seq[TaskLocation]]]
在start()方法中会初始化Actor,然后接收DAGSchedulerEvent处理。Scheduler会在SparkContext里start起来。
Event处理
源代码的阅读入口:能够依据processEvent(event:DAGSchedulerEvent)方法展开。
处理的事件包含这么一些:
Submit Job
JobSubmitted事件:
提交任务的事件传入參数例如以下
case JobSubmitted(jobId, rdd, func, partitions, allowLocal, callSite, listener, properties)
处理过程能够拆成三步看,每一步里面涉及的详细逻辑在以下补充展开
finalStage = newStage(rdd, partitions.size, None, jobId, Some(callSite))
本次newStage()操作能够相应新的result stage或者shuffle stage。返回Stage类(里面记录一些信息)。Stage类会传入Option[ShuffleDependency[_,_]]參数,内部有一个isShuffleMap变量,以标识该Stage是shuffle or result。
val job = new ActiveJob(jobId, finalStage, func, partitions, callSite, listener, properties)
ActiveJob类也是记录一些信息的类,能够当作是一个VO类
if (allowLocal && finalStage.parents.size == 0 && partitions.length == 1) {
// Compute very short actions like first() or take()
// with no parent stages locally.
listenerBus.post(SparkListenerJobStart(
job.jobId, Array[Int](), properties))
runLocally(job)
} else {
jobIdToActiveJob(jobId) = job
activeJobs += job
resultStageToJob(finalStage) = job
listenerBus.post(
SparkListenerJobStart(
job.jobId, jobIdToStageIds(jobId).toArray, properties))
submitStage(finalStage)
}
首先推断stage没有父亲依赖,且partition为1的话,就运行本地任务。否则,submitStage。
submitStage的逻辑为,首先寻找本次stage的parents。假设没有missing的parent stage,那么就submitMissingTask,即提交本次stage的tasks了。假设有,会对parent stage进行递归submitStage,并且getMissingParentStages得到的结果集是按id降序排的,也就是说递归submitStage的时候会按parent stage的id顺序进行。
submitMissingTask处理的是stage的parent已经available的stage。主要逻辑例如以下:
第一步:通过stage.isShuffleMap来决定生成ShuffleMapTask还是ResultTask,生成的ShuffleMapTask数目和partition数目相等。
第二步:把生成的tasks组建成一个TaskSet,提交给TaskScheduler的submitTasks方法。
TaskScheduler
DAGScheduler以stage为单位,提tasks给TaskScheduer,实现类为TaskSchedulerImpl。
TaskSchedulerImpl几个内部部件:
SchedulerBackend
SchedulableBuilder
DAGScheduler
TaskSetManager
TaskResultGetter
Tasks信息(taskIdToTaskSetId,taskIdToExecutorId,activeExecutorIds)
别的信息(SchedulerMode)
TaskScheduler做接收task、接收分到的资源和executor、维护信息、与backend打交道、把任务分配好等事情。
start(),stop()的时候,backend的start(),stop()
submitTasks(TaskSet)逻辑:
为这批Task生成新的TaskSetManager,把TaskSetManager加到SchedulerBuilder里,然后向backend进行一次reviveOffer()操作。
SchedulerBuilder
SchedulableBuilder有FIFO和Fair两种实现, addTaskSetManager会把TaskSetManager加到pool里。FIFO的话仅仅有一个pool。Fair有多个pool,Pool也分FIFO和Fair两种模式。
SchedulableBuilder的rootPool里面能够新增pool或者TaskSetManager,两者都是Scheduable的继承类,所以SchedulableBuilder用于维护rootPool这棵Scheduable 树结构。Pool是树上的非叶子节点,而TaskSetManager就是叶子节点。
在TaskScheduler初始化的时候会buildDafaultPool。
TaskSetManager
TaskSetManager负责这批Tasks的启动,失败重试,感知本地化等事情。每次reourseOffer方法会寻找合适(符合条件execId, host, locality)的Task并启动它。
reourseOffer方法,
def resourceOffer(
execId: String,
host: String,
maxLocality: TaskLocality.TaskLocality)
寻找符合execId, host和locality的task,找到的话就启动这个Task。启动的时候,把task加到runningTask的HashSet里,然后调DAGScheduler的taskStarted方法,taskStarted方法向eventProcessorActor发出BeginEvent的DAGSchedulerEvent。
TaskResultGetter
维护一个线程池,用来反序列化和从远端获取task结果。
def enqueueSuccessfulTask(taskSetManager: TaskSetManager, tid: Long, serializedData: ByteBuffer)
把序列化的数据反序列化解析出来之后,有两种情况:直接可读的result和间接task result。
前者是DirectTaskResult[T]类:
class DirectTaskResult[T](var valueBytes: ByteBuffer, var accumUpdates: Map[Long, Any], var metrics: TaskMetrics)
后者是IndirectTaskResult[T]类:
case class IndirectTaskResult[T](blockId: BlockId) extends TaskResult[T] with Serializable
在反解析出IndirectTaskResult后,能够得到BlockId这个类,他的实现有这么些:
在TaskResultGetter里,会通过blockManager的getRemoteBytes(BlockId)方法来获得序列化的task result,对这个task result进行反解析后得到DirectTaskResult类,从而获得反序列化后的真正结果数据。
这是大致的一个过程,详细另一些细节在之中,比方会向scheduler发送不同的event、blockManager会调用BlockManagerMaster把该Block remove掉。
BlockId类有这么些关键变量:
private[spark] sealed abstract class BlockId {
/** A globally unique identifier for this Block. Can be used for ser/de. */
def name: String // convenience methods
def asRDDId = if (isRDD) Some(asInstanceOf[RDDBlockId]) else None
def isRDD = isInstanceOf[RDDBlockId]
def isShuffle = isInstanceOf[ShuffleBlockId]
def isBroadcast = isInstanceOf[BroadcastBlockId]
以下看BlockManager怎样通过BlockId获得数据:
调用的是BlockManager的内部方法
private def doGetRemote(blockId: BlockId, asValues: Boolean): Option[Any] = {
require(blockId != null, "BlockId is null")
// 通过BlockManagerMaster获得这个blockId的locations
val locations = Random.shuffle(master.getLocations(blockId))
for (loc <- locations) {
logDebug("Getting remote block " + blockId + " from " + loc)
// 使用BlockManagerWorker来获得block的数据
val data = BlockManagerWorker.syncGetBlock(
GetBlock(blockId), ConnectionManagerId(loc.host, loc.port))
if (data != null) {
if (asValues) {
// 取到就返回
return Some(dataDeserialize(blockId, data))
} else {
return Some(data)
}
}
logDebug("The value of block " + blockId + " is null")
}
logDebug("Block " + blockId + " not found")
None
}
思路是通过BlockManagerMaster来获得block的位置信息,得到的集合打乱后,遍历位置信息,通过BlockManagerWorker去获得数据,仅仅要得到了,就反序列化之后返回。
在TaskResultGetter处理的时候,成功和失败分别向Scheduler调用handleSuccessfulTask和handleFailedTask方法。
handleSuccessfulTask在DAGScheduler里,会发出CompletionEvent事件,这一步结尾工作会有非常多细节处理,这里先不阅读了。
handleFailedTask的话,仅仅要TaskSetManager不是zombie,task没有被kill,那么会继续调用backend.reviveOffers()来re-run。
全文完 :)
Spark Core源代码分析: Spark任务运行模型的更多相关文章
- Spark Core源代码分析: Spark任务模型
概述 一个Spark的Job分为多个stage,最后一个stage会包含一个或多个ResultTask,前面的stages会包含一个或多个ShuffleMapTasks. ResultTask运行并将 ...
- Spark Core源代码分析: RDD基础
RDD RDD初始參数:上下文和一组依赖 abstract class RDD[T: ClassTag]( @transient private var sc: SparkContext, @tran ...
- Spark SQL源代码分析之核心流程
/** Spark SQL源代码分析系列文章*/ 自从去年Spark Submit 2013 Michael Armbrust分享了他的Catalyst,到至今1年多了,Spark SQL的贡献者从几 ...
- Spark SQL 源代码分析之 In-Memory Columnar Storage 之 in-memory query
/** Spark SQL源代码分析系列文章*/ 前面讲到了Spark SQL In-Memory Columnar Storage的存储结构是基于列存储的. 那么基于以上存储结构,我们查询cache ...
- Spark SQL 源代码分析之Physical Plan 到 RDD的详细实现
/** Spark SQL源代码分析系列文章*/ 接上一篇文章Spark SQL Catalyst源代码分析之Physical Plan.本文将介绍Physical Plan的toRDD的详细实现细节 ...
- Spark SQL 源代码分析系列
从决定写Spark SQL文章的源代码分析,到现在一个月的时间,一个又一个几乎相同的结束很快,在这里也做了一个综合指数,方便阅读,下面是读取顺序 :) 第一章 Spark SQL源代码分析之核心流程 ...
- Spark Core Runtime分析: DAGScheduler, TaskScheduler, SchedulerBackend
Spark Runtime里的主要层次分析,梳理Runtime组件和运行流程, DAGScheduler Job=多个stage,Stage=多个同种task, Task分为ShuffleMapTas ...
- Pig源代码分析: 简析运行计划的生成
摘要 本文通过跟代码的方式,分析从输入一批Pig-latin到输出物理运行计划(与launcher引擎有关,通常是MR运行计划.也能够是Spark RDD的运行算子)的总体流程. 不会详细涉及AST怎 ...
- 【Spark Core】任务运行机制和Task源代码浅析1
引言 上一小节<TaskScheduler源代码与任务提交原理浅析2>介绍了Driver側将Stage进行划分.依据Executor闲置情况分发任务,终于通过DriverActor向exe ...
随机推荐
- hdu 2155 小黑的镇魂曲(dp) 2008信息工程学院集训队——选拔赛
感觉蛮坑的一道题. 题意很像一个叫“是男人下100层”的游戏.不过多了个时间限制,要求在限定时间内从某一点下落到地面.还多了个最大下落高度,一次最多下落这么高,要不然会摔死. 一开始想dp的,然后想了 ...
- Android 嵌套GridView,ListView只显示一行的解决办法
重写ListView.GridView即可: public class MyListView extends ListView { public MyListView(Context context) ...
- 关于ThinkPHP中Session不能夸模块/控制器使用的问题-网上的答案我做个补充
1,确保c:/windows目录下有php.ini文件2,修改php.ini中的session.auto_start = 0 为 session.auto_start = 1 //设置自动开启ses ...
- 转载: Vim 练级攻略
转自:http://coolshell.cn/articles/5426.html 酷壳 vim的学习曲线相当的大(参看各种文本编辑器的学习曲线),所以,如果你一开始看到的是一大堆VIM的命令分类, ...
- linux 命令——文件管理 ls
一.介绍 ls命令是linux下最常用的命令之一,ls跟dos下的dir命令是一样的都是用来列出目录下的文件和子目录.ls全称list,即列表. 缺省下ls用来打印出当前目录的清单,如果ls指定其他目 ...
- java Ant 的使用
Apache Ant 1.7.0 is the best available version的下载地址:http://ant.apache.org/bindownload.cgi 部署: 参考JAVA ...
- Away 3D 之 交互和渐变----Interactivity and Tweening
在这个教程中,你将学会如何创建一个地板对象,本教程中的地板是可交互的并且能够移动小方块到鼠标的点击的地方. 1. 设置场景: 你正在创建的场景包含了一个平面,地板和一个看起来像一个饰品的方块,还有一个 ...
- SVM应用
我在项目中应用的SVM库是国立台湾大学林智仁教授开发的一套开源软件,主要有LIBSVM与LIBLINEAR两个,LIBSVM是对非线性数据进行分类,大家也比较熟悉,LIBLINEAR是对线性数据进行分 ...
- JAVA与图形界面开发(Applet应用程序、AWT库、Swing)
Applet 1)简单说,Applet就是嵌入到网页中的小程序,Java代码. 2)编写Applet程序,要继承JApplet类,并根据自己需要覆写相关方法(init.start.stop.destr ...
- HDU5744:Keep On Movin(字符串)
题意: 给出t组测试数据,每组给出正整数n表示有n种字符,接下来给出n个数表示该种字符的数目,操作一下,使得可以构造的最小回文串字符数目最大且输出. 分析: 如果每个字符出现次数都是偶数, 那么答案显 ...