本課主題

  • 通过 Spark-shell 窥探程序运行时的状况
  • TaskScheduler 与 SchedulerBackend 之间的关系
  • FIFO 与 FAIR 两种调度模式彻底解密
  • Task 数据本地性资源分配源码实现

引言

TaskScheduler 是 Spark 整个调度的底层调度器,底层调度器是负责具体 Task 本身的运行的,所以豪无疑问的是一个至关重要的内容。希望这篇文章能为读者带出以下的启发:

  • 了解 程序运行时具体创建的实例对象
  • 了解 TaskScheduler 与 SchedulerBackend 之间的关系
  • 了解 FIFO 与 FAIR 两种调度模式彻底解密
  • 了解 Task 数据本地性资源分配源码实现

通过 Spark-shell 窥探程序运行时的状况

首先通过启动一个 Spark-shell 来观察具体 TaskScheduler 实例的情况,在这个过程中我们可以看见上一节所讲的 SparkDeploySchedulerBackend 和 AppClient 的身影,这是因为 Spark-shell 本身也是一个应用程序。当我们启动 Spark-shell 本身的时候命令终端反馈回来的主要是 ClientEndpoint 和 SparkDeploySchedulerBackend,这是因为此时还没有任何 Job 的触发,这是启动 Application 本身而已,所以主要就是实例化 SparkContext 并注册当前的应用程序给 Master 且从集群中获得 ExecutorBackend 的计算资源。(详情可以参考第28课:Spark天堂之门解密的博客)。

[下图是 SparkShell 启动后打印的日志信息]

上图出现了 AppClient 和 ClientEndPoint,你可以很清晰的看见 ClientEndPoint 注册给 Master (e.g. ClientEndPoint connecting to master spark://HadoopM:7077)。下一行有 SparkDeploySchedulerBackend,接著几行日志是 AppClient Executor Added;这几行日志证明了程序在启动并注册时,是交给 SparkDeploySchedulerBackend 来管理 Executor,中间过程也会创建BlockManagerMaster。

SchedulerBackend 在最后表示自己已经准备好了 (e.g. SchedulaerBackend is ready for scheduling beginning after reached minRegisteredResourcesRatio),这说明一件事情,在创建 SparkContext 之前会先创建很多其他功能的实例对象,那些 AppClient、SparkDeploySchedulerBackend、BlockManagerMaster 都是随著 SparkContext 创建而创建的!!!

[下图是 SparkShell 启动后打印的日志信息]

思考题:为什么启动 Spark-shell 的时候没有看见 DriverEndPoint?因为上述打印的日志是从一个应用程序的角度去考虑的,而不是从应用程序运行一个 Job 的层面去考虑的,一个应该程序启动时因为程序要向 Master 注册,所以当然有 AppClient 和 ClientEndPoint 的参与; 而 DriverEndPoint 是在XX时候创建的,因为此时没有运行任何的 Job,所以还没需要 DriverEndPoint 的参与。所以你可以通过观察 Spark-shell 以及应用程序运行的角度,你可以很清楚的看见 "应用程序" 和 "应用程序Job的运行" 是两种不同类型的事情。

作业提交的日志

接著提交了一个 Spark 应用程序 e.g. HelloSpark Wordcount。我们是调用了 foreach 来触发一个 Action,所以中间部份你可以看到它表示 Starting job。在 Starting Job 下一行日志看到当它触发一个 Job 时,首先把 Job 交给 DAGScheduler 来注册 RDD,然后获得一个 Job,然后划分成不同的 Stage (e.g. Final Stage),日志中看到它只有一个 ShuffleMapTask 和 一个 ResultStage;然后它提交的时候,首先提交 ShuffleMapStage 给 TaskSchedulerImpl,图中看见只有 1 个任务,此时已经提交给底层了。

任务是以 TaskSet 的方式提交给底层,同时创建了 TaskSetManager 去管理这个任务,TaskSetManager 知道这个任务的启动与结束,然后等待任务运行完毕后又会再次交给 DAGScheduler 说明任务已经完成。

[下图是 SparkShell 作业提交后打印的日志信息]

由于上一个 Stage 是 ShuffleMapTask,所以下一个 Stage 如果要运行的话,要先通过 MapOutputTrackerMasterEndpoint 来获取上一个阶段的输出。不过要注意一下,TaskSetManager 在启动的时候会具体说自己在那个 Stage 、那台机器上、那个 Partition 和是否是数据本地性。数据本地性有几种实现的方式。(e.g. NODE_LOCAL 是数据就在当前机器的磁盘上)

[下图是 SparkShell 作业提交后打印的日志信息]

我们得出的结论是 DAGScheduler 划分好 Stage 之后会通过 TaskSchedulerImpl 中的 TaskSetManager 来管理当前要运行 Stage 中所有的任务 TaskSet,它是一个包含了高层调度器与底层调度器的一个集合。TaskSet 的第一个成员是一个数组;第二个成员表示自己属于那一个 Stage,第三个成员是 StageAttemptId,第四个是优先值,调度时底层有一个调度池,这个调度池会规定每个 Stage 提交后具体运行的优先级。

TaskSetManager 在实例化的时候要完成 TaskSchedulerImpl 的工作,因为它是 TaskSet 的管理者,所以它其中的一个成员肯定是 TaskSet,还有一个成员是每个任务最大的重试次数。TaskSetManager 会根据 locality aware 来为 Task 分配计算资源、监控 Task 的执行状态 (例如重试、慢任务进行推测式执行等,调度的时候底层有一个调度池)

TaskScheduler 与 SchedulerBackend 之间的关系

他们两者之间的关系是一个是高层调度器、一个是底层调度器;一个负责 Stage 的划分、一个是负责把任务发送给 Executor 去执行并接收运行结果。

应用程序的资源分配在应用程序启动时已经完成,现在要考虑的是具体应用程序中每个任务到底要运行在那个 ExecutorBackend 上,现在是任务的分配。TaskScheduler 要负责为 Task 分配计算资源:此时程序已经分配好集群中的计算资源了,然后会根据计算本地性原则来确定 Task 具体要运行在那个 ExecutorBackend 中:

  1. 这里有两种不同的 Task,一种是 ShuffleMapTask,一种是 ResultMapTask
    [下图是 DAGScheduler.scala 中 submitMissingTasks 方法中内部具体的实现]
  2. DAGScheduler 完成面向 Stage 的划分之后,会按照顺序将每个 Stage 通过 TaskSchedulerImpl 的 Submit Task 提交给底层调度器 (提交作业啦!!!) TaskSchedulerImpl.submitTasks: 主要的作用是將 TaskSet 加入到 TaskSetManager 中進行管理;
    [下图是 DAGScheduler.scala 中 submitMissingTasks 方法中内部具体的实现]
  3. 高层调度器 DAGScheduler 提交了任务是通过调用 submitTask 方法提交 TaskSet 给底层调度器,然后赋值给一个变量 Task,同时创建了一个 TaskSetManager 的实例,这个很关键,它传入了 taskSchedulerImpl 对象本身、TaskSet 和最大失败后自动重试的次数。
    [下图是 TaskSchedulerImpl.scala 中 submitTasks 方法]

    [下图是 TaskSchedulerImpl.scala 中 createTaskSetManager 方法]
  4. 创建 SparkContext 中调用了 createTaskScheduler 来创建 TaskSchedulerImpl 的实例,默认作业失败后自动重试的次数是 4 次。
    [下图是 SparkContext.scala 中创建三大核心对象的代码实现]

    [下图是 TaskSchedulerImpl.scala 中类和主构造器]
  5. 比较关键的地方是调用了 schedulableBuilder 中的 addTaskSetManager,SchedulableBuilder 本身是应用程序级别的调度器,它自己支持两种调度模式。SchedulableBuilder 会确定 TaskSetManager 的调度顺序,然后按照 TaskSetManager 的 locality aware 来确定每个 Task 具体运行在那个 ExecutorBackend 中;补充说明:schedulableBuilder 是在创建 TaskSchedulerImpl 时实例化的。
    [下图是 SchedulableBuilder.scala 中的方法]

    一种是FIFO; 另一种是FAIR,调度策略可以通过 spark-env.sh 中的 spark.scheduler.mode 进行具体的设置,默认情况下是 FIFO
    [下图是 SparkContext.scala 中 createTaskScheduler 方法内部具体的实现]

    [下图是 TaskScheduler.Impl 中 initialize 方法]

    [下图是 TaskScheduler.Impl.scala 中 schedulingMode 变量的具体实现]
  6. 从第3步 submitTask 方法中最后调用了 backend.revivOffers 方法。这是 CoarseGrainedSchedulerBackend.reviveOffers: 给 DrivereEndpoint 发送 ReviveOffers,DriverEndPoint 是驱动程序的调度器;
    [下图是 CoarseGrainedSchedulerBackend.scala 中 reviveOffers 方法]

    [下图是 CoarseGrainedSchedulerBackend.scala 中 DriverEndPoint 类里的 start 方法]

    [下图是 CoarseGrainedSchedulerBackend.scala 中 DriverEndPoint 类]

    [下图是 CoarseGrainedSchedulerBackend.scala 中 DriverEndPoint 类里的 receive 方法内部具体的实现]

    ReviveOffers 本身是一个空的 case object 对象,只是起到触发底层资源触发调度的作用,在有 Task 提交或者计算资源变动的时候会发送 ReviveOffers 这个消息作为触发器;
    [下图是 CoarseGrainedClusterMessage.scala 中 ReviveOffers case object]
  7. 在 DriverEndpoint 接受 ReviveOffers 消息并路由到 makeOffers 具体的方法中;在 makeOffers 方法中首先准备好所有可以用于计算的 Executor,然后找出可以的 workOffers (代表了所有可用 ExecutorBackend 中可以使用的 CPU Cores 信息)WorkerOffer 会告我们具体 Executor 可用的资源,比如说 CPU Core,为什么此时不考虑内存只考虑 CPU Core,因为在这之前已经分配好了。
    [下图是 CoarseGrainedSchedulerBackend.scala 中 makeOffers 方法]

    [下图是 WorkerOffer.scala 中 WorkerOffer case class]
  8. 而確定 Task 具體運行在那個 ExecutorBackend 上的算法是由 TaskSetManager 的 resourceOffer 的方法決定。TaskScheduerImpl.resourceOffers: 为每一个Task 具体分配计算资源,输入是 ExcutorBackend 及其上可用的 Cores,输出 TaskDescription 的二位数组,在其中确定了每个 Task 具体运行在哪个 ExecutorBackend: resourceOffers 到底是如何确定 Task 具体运行在那个 ExecutorBackend 上的呢?算法的实现具体如下:
    [下图是 TaskSchedulerImpl.scala 中 resourceOffers 方法]

    [下图是 TaskSchedulerImpl.scala 中 resourceOffers 方法内部具体的实现]
    • 通过 Random.shuffle 方法重新洗牌所有的计算以寻找以计算的负载均衡;
    • 根据每个 ExecutorBackend 的 cores 的个数声明类行为 TaskDescription 的 ArrayBuffer 数组
    • 打散的是 Executor 的资源,这样有随机性,随机性有利于负载均衡;
    • 如果有新的 ExecutorBackend 分配给我们的 Job 此时会调用 ExecutorAdded 来获得最新的完整的可用计算资源。
      [下图是 TaskSetManager.scala 中 executorAdded 方法]
    • 优先本地性从高到低依次为:PROCESS_LOCAL、NODE_LOCAL、NO_PREF、RACK_LOCAL、ANY. 其中 NO_PREF 是指机器本地性。一台机器通常就只有一个 Node。我们追求的是 Node 的本地性高于机器本地性。每个 Task 默认是采用一个线程进行计算的。
      [下图是 TaskSetManager.scala 中 computeValidLocalityLevels 方法]
  9. 从 TaskSchedulerImpl.scala 中的 resourceOffers 后续调用了 resourceOfferSignleTask 来确定了具体任务运行在那台机制上
    [下图是 TaskSchedulerImpl.scala 中 resourceOfferSingleTask 方法]
  10. 通过调用 TaskSetManager 的 resourceOffer 最终确定每个 Task 具体运行在哪个 ExecutorBackend 的具体 Locality Level;
  11. 在第7步调用makeOffers方法后,再通过 launchTasks 把任务发送给 ExecutorBackend 去执行
    [下图是 CoarseGrainedSchedulerBackend.scala 中 launchTasks 方法]
  12. DAGScheduler 是从数据层面考虑 preferredLocation 的,而 TaskScheduler 是从具体计算 Task 的角度考虑计算的本地性 Task 进行广播时候的 AkkFrameSize 大小是 128MB,这样改好处是可以广播大任务。如果任务大于等于 128 MB - 200K 的话则 Task 会直接被丢弃掉。如果小于 128 MB - 200K 会通过 CoarseGraninedExecutorBackend 去 launchTask 到具体的 ExecutorBackend 上。
    [下图是 CoarseGrainedSchedulerBackend.scala 中 akkaFrameSize 变量]

    [下图是 AkkaUtils.scala 中 maxFrameSizeBytes 方法]

总结

参考资料

资料来源来至 DT大数据梦工厂 大数据传奇行动 第36课:TaskScheduler内幕天机解密:Spark shell案例运行日志详解、TaskScheduler和SchedulerBackend、FIFO与FAIR、Task运行时本地性算法详解等

Spark源码图片取自于 Spark 1.6.0版本

[Spark内核] 第36课:TaskScheduler内幕天机解密:Spark shell案例运行日志详解、TaskScheduler和SchedulerBackend、FIFO与FAIR、Task运行时本地性算法详解等的更多相关文章

  1. TaskScheduler内幕天机解密:Spark shell案例运行日志详解、TaskScheduler和SchedulerBackend、FIFO与FAIR、Task运行时本地性算法详解等

    本课主题 通过 Spark-shell 窥探程序运行时的状况 TaskScheduler 与 SchedulerBackend 之间的关系 FIFO 与 FAIR 两种调度模式彻底解密 Task 数据 ...

  2. [Spark内核] 第40课:CacheManager彻底解密:CacheManager运行原理流程图和源码详解

    本课主题 CacheManager 运行原理图 CacheManager 源码解析 CacheManager 运行原理图 [下图是CacheManager的运行原理图] 首先 RDD 是通过 iter ...

  3. [Spark内核] 第34课:Stage划分和Task最佳位置算法源码彻底解密

    本課主題 Job Stage 划分算法解密 Task 最佳位置算法實現解密 引言 作业调度的划分算法以及 Task 的最佳位置的算法,因为 Stage 的划分是DAGScheduler 工作的核心,这 ...

  4. [Spark内核] 第31课:Spark资源调度分配内幕天机彻底解密:Driver在Cluster模式下的启动、两种不同的资源调度方式源码彻底解析、资源调度内幕总结

    本課主題 Master 资源调度的源码鉴赏 [引言部份:你希望读者看完这篇博客后有那些启发.学到什么样的知识点] 更新中...... 资源调度管理 任务调度与资源是通过 DAGScheduler.Ta ...

  5. [Spark内核] 第35课:打通 Spark 系统运行内幕机制循环流程

    本课主题 打通 Spark 系统运行内幕机制循环流程 引言 通过 DAGScheduelr 面向整个 Job,然后划分成不同的 Stage,Stage 是從后往前划分的,执行的时候是從前往后执行的,每 ...

  6. [Spark内核] 第33课:Spark Executor内幕彻底解密:Executor工作原理图、ExecutorBackend注册源码解密、Executor实例化内幕、Executor具体工作内幕

    本課主題 Spark Executor 工作原理图 ExecutorBackend 注册源码鉴赏和 Executor 实例化内幕 Executor 具体是如何工作的 [引言部份:你希望读者看完这篇博客 ...

  7. [Spark内核] 第37课:Task执行内幕与结果处理解密

    本课主题 Task执行内幕与结果处理解密 引言 这一章我们主要关心的是 Task 是怎样被计算的以及结果是怎么被处理的 了解 Task 是怎样被计算的以及结果是怎么被处理的 Task 执行原理流程图 ...

  8. [Spark内核] 第28课:Spark天堂之门解密

    本課主題 什么是 Spark 的天堂之门 Spark 天堂之门到底在那里 Spark 天堂之门源码鉴赏 引言 我说的 Spark 天堂之门就是SparkContext,这篇文章会从 SparkCont ...

  9. [Spark内核] 第29课:Master HA彻底解密

    本课主题 Master HA 解析 Master HA 解析源码分享 [引言部份:你希望读者看完这篇博客后有那些启发.学到什么样的知识点] 更新中...... Master HA 解析 生产环境下一般 ...

随机推荐

  1. 如何在Raspberry Pi 3B中安装Windows 10 IoT Core

    Windows 10 IoT Core简介 Windows 10 IoT是微软专门为物联网生态打造的操作系统,Windows 10 IoT Core则是Windows 10 IoT 操作系统的核心版本 ...

  2. 企业级Tomcat安全管理优化方案

    telnet管理端口保护 1. 修改默认的8005管理端口为不易猜测的端口(大于1024): <Server port=" shutdown="dangerous" ...

  3. 从 Bridge 到 OVS,探索虚拟交换机

    Linux Bridge 和物理网络一样,虚拟网络要通信,必须借助一些交换设备来转发数据.因此,对于网络虚拟化来说,交换设备的虚拟化是很关键的一环. 上文「网络虚拟化」已经大致介绍了 Linux 内核 ...

  4. kafka消息传输时的对象转字符串时所需 -json String 转list 、set、 Long、 String 、map 与json Iterator遍历

    JSONObject jsonObject = new JSONObject(jsonString); Iterator iterator = jsonObject.keys(); while(ite ...

  5. Android笔记(五)利用Intent启动活动

    Intent是意图的意思,分为显式 Intent 和隐式 Intent. 以下我们试图在FirstActivity中通过点击button来启动SecondActivity 1.显式Intent 在应用 ...

  6. python 中Dict 转 Json

    近期在公司须要写个小工具.运用到的python,然后须要将Dict转成Json. 之前遇到转换Json失败,然后以为复杂的Entity结构.不能用Json的库Json.dump().进行转换. 自己些 ...

  7. Maste Note for OCR / Vote disk Maintenance Operations (ADD/REMOVE/REPLACE/MOVE)

    Doc ID 428681.1 Applies to: Oracle Database - Enterprise Edition - Version 10.2.0.1 to 11.2.0.1.0 [R ...

  8. vue-router实例

    最近刚刚用vue写了个公司项目,使用vue-cli构建的,算是中大型项目吧,然后这里想记录并且分享一下其中的知识点,希望对大家有帮助,后期会逐渐分享:话不多说,直接上代码!! main.js // T ...

  9. 【Java入门提高篇】Day7 Java内部类——局部内部类

    今天介绍第二种内部类--局部内部类. 局部内部类是什么?顾名思义,那就是定义在局部内部的类(逃).开玩笑的,局部内部类就是定义在代码块.方法体内.作用域(使用花括号"{}"括起来的 ...

  10. Django的缓存机制

    由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5 ...