在前面的章节Client的加载中,Spark的DriverRunner已开始执行用户任务类(比如:org.apache.spark.examples.SparkPi),下面我们开始针对于用户任务类(或者任务代码)进行分析
 
一、整体预览
          基于上篇图做了扩展,增加任务执行的相关交互
     
  • Code:指的用户编写的代码
  • RDD:弹性分布式数据集,用户编码根据SparkContext与RDD的api能够很好的将Code转化为RDD数据结构(下文将做转化细节介绍)
  • DAGScheduler:有向无环图调度器,将RDD封装为JobSubmitted对象存入EventLoop(实现类DAGSchedulerEventProcessLoop)队列中
  • EventLoop: 定时扫描未处理JobSubmitted对象,将JobSubmitted对象提交给DAGScheduler
  • DAGScheduler:针对于JobSubmitted进行处理,最终将RDD转化为执行TaskSet,并将TaskSet提交至TaskScheduler
  • TaskScheduler: 根据TaskSet创建TaskSetManager对象存入SchedulableBuilder的数据池(Pool)中,并调用DriverEndpoint唤起消费(ReviveOffers)操作
  • DriverEndpoint:接受ReviveOffers指令后将TaskSet中的Tasks根据相关规则均匀分配给Executor
  • Executor:启动一个TaskRunner执行一个Task
 
二、Code转化为初始RDDs
          我们的用户代码通过调用Spark的Api(比如:SparkSession.builder.appName("Spark Pi").getOrCreate()),该Api会创建Spark的上下文(SparkContext),当我们调用transform类方法 (如:parallelize(),map())都会创建(或者装饰已有的) Spark数据结构(RDD), 如果是action类操作(如:reduce()),那么将最后封装的RDD作为一次Job提交,存入待调度队列中(DAGSchedulerEventProcessLoop )待后续异步处理。
          如果多次调用action类操作,那么封装的多个RDD作为多个Job提交。
     流程如下:
     
  • ExecuteEnv(执行环境 )
    • 这里可以是通过spark-submit提交的MainClass,也可以是spark-shell脚本
    • MainClass : 代码中必定会创建或者获取一个SparkContext
    • spark-shell:默认会创建一个SparkContext
  • RDD(弹性分布式数据集)
    • create:可以直接创建(如:sc.parallelize(1 until n, slices) ),也可以在其他地方读取(如:sc.textFile("README.md"))等
    • transformation:rdd提供了一组api可以进行对已有RDD进行反复封装成为新的RDD,这里采用的是装饰者设计模式,下面为部分装饰器类图
    • action:当调用RDD的action类操作方法时(collect、reduce、lookup、save ),这触发DAGScheduler的Job提交
  • DAGScheduler:创建一个名为JobSubmitted的消息至DAGSchedulerEventProcessLoop阻塞消息队列(LinkedBlockingDeque)中
  • DAGSchedulerEventProcessLoop:启动名为【dag-scheduler-event-loop】的线程实时消费消息队列
  • 【dag-scheduler-event-loop】处理完成后回调JobWaiter
  • DAGScheduler:打印Job执行结果
  • JobSubmitted:相关代码如下(其中jobId为DAGScheduler全局递增Id):
eventProcessLoop.post(JobSubmitted(
jobId, rdd, func2, partitions.toArray, callSite, waiter,
SerializationUtils.clone(properties)))
  • 最终示例:
     最终转化的RDD分为四层,每层都依赖于上层RDD,将ShffleRDD封装为一个Job存入DAGSchedulerEventProcessLoop待处理,如果我们的代码中存在几段上面示例代码,那么就会创建对应对的几个ShffleRDD分别存入DAGSchedulerEventProcessLoop
 
三、RDD分解为待执行任务集合(TaskSet
     Job提交后,DAGScheduler根据RDD层次关系解析为对应的Stages,同时维护Job与Stage的关系。
     将最上层的Stage根据并发关系(findMissingPartitions )分解为多个Task,将这个多个Task封装为TaskSet提交给TaskScheduler。非最上层的Stage的存入处理的列表中(waitingStages += stage)
 流程如下:
  
  • DAGSchedulerEventProcessLoop中,线程【dag-scheduler-event-loop】处理到JobSubmitted
  • 调用DAGScheduler进行handleJobSubmitted
    • 首先根据RDD依赖关系依次创建Stage族,Stage分为ShuffleMapStage,ResultStage两类

    • 更新jobId与StageId关系Map
    • 创建ActiveJob,调用LiveListenerBug,发送SparkListenerJobStart指令
    • 找到最上层Stage进行提交,下层Stage存入waitingStage中待后续处理
      • 调用OutputCommitCoordinator进行stageStart()处理
      • 调用LiveListenerBug, 发送 SparkListenerStageSubmitted指令
      • 调用SparkContext的broadcast方法获取Broadcast对象
      • 根据Stage类型创建对应多个Task,一个Stage根据findMissingPartitions分为多个对应的Task,Task分为ShuffleMapTask,ResultTask

      • 将Task封装为TaskSet,调用TaskScheduler.submitTasks(taskSet)进行Task调度,关键代码如下:
taskScheduler.submitTasks(new TaskSet(
tasks.toArray, stage.id, stage.latestInfo.attemptId, jobId, properties))
 
四、TaskSet封装为TaskSetManager并提交至Driver
     TaskScheduler将TaskSet封装为TaskSetManager(new TaskSetManager(this, taskSet, maxTaskFailures, blacklistTrackerOpt)),存入待处理任务池(Pool)中,发送DriverEndpoint唤起消费(ReviveOffers)指令
      
  • DAGSheduler将TaskSet提交给TaskScheduler的实现类,这里是TaskChedulerImpl
  • TaskSchedulerImpl创建一个TaskSetManager管理TaskSet,关键代码如下:
     new TaskSetManager(this, taskSet, maxTaskFailures, blacklistTrackerOpt)
  • 同时将TaskSetManager添加SchedduableBuilder的任务池Poll中
  • 调用SchedulerBackend的实现类进行reviveOffers,这里是standlone模式的实现类StandaloneSchedulerBackend
  • SchedulerBackend发送ReviveOffers指令至DriverEndpoint
 
五、Driver将TaskSetManager分解为TaskDescriptions并发布任务到Executor
     Driver接受唤起消费指令后,将所有待处理的TaskSetManager与Driver中注册的Executor资源进行匹配,最终一个TaskSetManager得到多个TaskDescription对象,按照TaskDescription想对应的Executor发送LaunchTask指令
当Driver获取到ReviveOffers(请求消费)指令时
  • 首先根据executorDataMap缓存信息得到可用的Executor资源信息(WorkerOffer),关键代码如下
val activeExecutors = executorDataMap.filterKeys(executorIsAlive)
val workOffers = activeExecutors.map { case (id, executorData) =>
new WorkerOffer(id, executorData.executorHost, executorData.freeCores)
}.toIndexedSeq
  • 接着调用TaskScheduler进行资源匹配,方法定义如下:
     def resourceOffers(offers: IndexedSeq[WorkerOffer]): Seq[Seq[TaskDescription]] = synchronized {..}
    • 将WorkerOffer资源打乱(val shuffledOffers = Random.shuffle(offers))
    • 将Poo中待处理的TaskSetManager取出(val sortedTaskSets = rootPool.getSortedTaskSetQueue),
    • 并循环处理sortedTaskSets并与shuffledOffers循环匹配,如果shuffledOffers(i)有足够的Cpu资源( if (availableCpus(i) >= CPUS_PER_TASK) ),调用TaskSetManager创建TaskDescription对象(taskSet.resourceOffer(execId, host, maxLocality)),最终创建了多个TaskDescription,TaskDescription定义如下:
new TaskDescription(
taskId,
attemptNum,
execId,
taskName,
index,
sched.sc.addedFiles,
sched.sc.addedJars,
task.localProperties,
serializedTask)
  • 如果TaskDescriptions不为空,循环TaskDescriptions,序列化TaskDescription对象,并向ExecutorEndpoint发送LaunchTask指令,关键代码如下:
for (task <- taskDescriptions.flatten) {
val serializedTask = TaskDescription.encode(task)
val executorData = executorDataMap(task.executorId)
executorData.freeCores -= scheduler.CPUS_PER_TASK
executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))
}

【Spark2.0源码学习】-9.Job提交与Task的拆分的更多相关文章

  1. 【Spark2.0源码学习】-1.概述

          Spark作为当前主流的分布式计算框架,其高效性.通用性.易用性使其得到广泛的关注,本系列博客不会介绍其原理.安装与使用相关知识,将会从源码角度进行深度分析,理解其背后的设计精髓,以便后续 ...

  2. spark2.0源码学习

    [Spark2.0源码学习]-1.概述 [Spark2.0源码学习]-2.一切从脚本说起 [Spark2.0源码学习]-3.Endpoint模型介绍 [Spark2.0源码学习]-4.Master启动 ...

  3. 【Spark2.0源码学习】-2.一切从脚本说起

    从脚本说起      在看源码之前,我们一般会看相关脚本了解其初始化信息以及Bootstrap类,Spark也不例外,而Spark我们启动三端使用的脚本如下: %SPARK_HOME%/sbin/st ...

  4. 【Spark2.0源码学习】-3.Endpoint模型介绍

         Spark作为分布式计算框架,多个节点的设计与相互通信模式是其重要的组成部分.   一.组件概览      对源码分析,对于设计思路理解如下:            RpcEndpoint: ...

  5. 【Spark2.0源码学习】-4.Master启动

         Master作为Endpoint的具体实例,下面我们介绍一下Master启动以及OnStart指令后的相关工作   一.脚本概览      下面是一个举例: /opt/jdk1..0_79/ ...

  6. 【Spark2.0源码学习】-5.Worker启动

         Worker作为Endpoint的具体实例,下面我们介绍一下Worker启动以及OnStart指令后的额外工作   一.脚本概览      下面是一个举例: /opt/jdk1..0_79/ ...

  7. 【Spark2.0源码学习】-6.Client启动

    Client作为Endpoint的具体实例,下面我们介绍一下Client启动以及OnStart指令后的额外工作 一.脚本概览      下面是一个举例: /opt/jdk1..0_79/bin/jav ...

  8. 【Spark2.0源码学习】-10.Task执行与回馈

         通过上一节内容,DriverEndpoint最终生成多个可执行的TaskDescription对象,并向各个ExecutorEndpoint发送LaunchTask指令,本节内容将关注Exe ...

  9. 【Spark2.0源码学习】-7.Driver与DriverRunner

         承接上一节内容,Client向Master发起RequestSubmitDriver请求,Master将DriverInfo添加待调度列表中(waitingDrivers),下面针对于Dri ...

随机推荐

  1. 程序设计 之 C#实现《拼图游戏》

    功能描述: 1.用户自定义上传图片 2.游戏难度选择:简单(3*3).一般(5*5).困难(9*9)三个级别 3.纪录完成步数 模块: 1.拼图类 2.配置类 3.游戏菜单窗口 4.游戏运行窗口 -- ...

  2. Linux 安装SVN服务器 (转)

    一. SVN 简介 Subversion(SVN) 是一个开源的版本控制系統, 也就是说 Subversion 管理着随时间改变的数据. 这些数据放置在一个中央资料档案库 (repository) 中 ...

  3. 在Centos中yum安装和卸载软件的使用方法

    安装一个软件时 yum -y install httpd 安装多个相类似的软件时 yum -y install httpd* 安装多个非类似软件时 yum -y install httpd php p ...

  4. 学Java,是自学还是去培训班学习?

    现在正在读在校的最后一个学年,想毕业后从事编程,但是感觉自己技术太差,应该是培训呢?还是去找实习?亦或是有更好的途径? 对于 Android 目前的行业趋势,不知道自己该不该坚持?还是转其他行业? 已 ...

  5. ios ALAssetsLibrary简单的使用

    关于ALAssetsLibrary的简单使用有两个方面: 第一:存储图片/视频方法如下: // With a UIImage, the API user can use -[UIImage CGIma ...

  6. 装饰器模式(Decorator)——深入理解与实战应用

    本文为原创博文,转载请注明出处,侵权必究! 1.初识装饰器模式 装饰器模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能.其结构图如下: Component为统一接口,也是装饰类和被装 ...

  7. 关于在网页拼接时出现:提示Uncaught SyntaxError: missing ) after argument list;错误的原因分析

    1:网页拼接不完善,可能哪里漏了:),},</XX>...等 2:如果有动态数据写入的话,请注意转义动态数据,如图(是转义后的内容,不会报错): 在写方法时:onclick中,注意单双引号 ...

  8. 5 安装Alloc服务

    cnblogs-DOC 1.服务器环境 2.安装Redis3.安装Zookeeper4.安装MPush5.安装Alloc服务6.完整测试7.常见问题 一.Linux安装Mpush-Alloc [roo ...

  9. 透视I/O多路复用

    透视I/O多路复用 我写的不是select这些函数的教学,需要了解的请自行Google或者去man,这些是帮助我理解函数的封装之下的道理. 需要回答的问题 I/O准备好了指什么?什么叫I/O已经可读/ ...

  10. 149_best-time-to-buy-and-sell-stock

    /*@Copyright:LintCode@Author:   Monster__li@Problem:  http://www.lintcode.com/problem/best-time-to-b ...