Spark 入门之十二:再看Spark中的调度策略(Standlone)

来源:互联网 发布:中国人寿网络大学 编辑:程序博客网 时间:2024/06/07 01:42

资源调度是Spark中比较重要的内容,对调度的相关原理以及策略的了解对集群的运行以及优化都会有很大的帮助,资源调度的方式有多种,Local,Standlone,Yarn,Mesos等,本文只针对Standlone的方式做简介

几个重要的概念

开始文章之前,再次对几个核心的概念做一个总结

被调用对象

  • Application:Spark 的应用程序,用户提交后,Spark为App分配资源,将程序转换并执行,其中Application包含一个Driver program和若干Executor
  • Job:一个RDD Graph触发的作业,往往由Spark Action算子触发,在SparkContext中通过runJob方法向Spark提交Job
  • Stage:每个Job会根据RDD的宽依赖关系被切分很多Stage, 每个Stage中包含一组相同的Task, 这一组Task也叫TaskSet

  • Task:一个分区对应一个Task,Task执行RDD中对应Stage中包含的算子。Task被封装好后放入Executor的线程池中执行

调用参与方

  • Driver:运行Application的main()函数并且创建SparkContext

  • Worker Node:集群中任何可以运行Application代码的节点,运行一个或多个Executor进程

  • Executor:是为Application运行在Worker node上的一个进程,该进程负责运行Task,并且负责将数据存在内存或者磁盘上。每个Application都会申请各自的Executor来处理任务
  • SparkContext:Spark 应用程序的入口,负责调度各个运算资源,协调各个 WorkerNode 上的 Executor

  • DAGScheduler:根据Job构建基于Stage的DAG,并提交Stage给TaskScheduler

  • TaskScheduler:将Taskset提交给Worker node集群运行并返回结果,一个应用对应一个TaskScheduler
  • TaskSetManager:每个Stage对应一个TaskSetManager,调度的时候以TaskSetManager为调度单位
  • ExecutorBackend:在Worker上执行Task的线程组
  • SchedulerBackend:主要用来与Worker中的ExecutorBackend建立连接,用来向Executor发送要执行任务,或是接受执行任务的结果,也可以用来创建AppClient(包装App信息,包含可以创建CoarseGrainedExecutorBackend实例Command),用于向Master汇报资源需求

SparkContext的初始化以及交互

SparkContext的初始化

SparkContext是开发Spark应用的入口,它负责和整个集群的交互,包括创建RDD,accumulators and broadcast variables。理解Spark的架构,需要从这个入口开始。下图是官网的架构图。
这里写图片描述

DriverProgram就是用户提交的程序,这里边定义了SparkContext的实例。SparkContext定义在core/src/main/scala/org/apache/spark/SparkContext.scala。

SparkContext的主要初始化工作,主要是在SparkContext.scala的一个超大的try…catch 块中,其中主要完成了以下几件事情

  • SparkConf的检测,包括默认值的设置
  • EventLog的初始化
  • 状态检测对象的初始化
  • Application WebUI的初始化
  • Executor环境变量的配置
  • 启动心跳维持监听actor,因为executors需要同driver维持心跳
  • 调用createTaskScheduler方法,根据masters uri的格式,确定schedulerBackend和taskScheduler的取值,该过程还会调用schedtaskScheduler的initialize方法确定task pool的调度算法
  • 由AppClient/ClientActor代理,同masters交互,确定可用master,由后者代理获取并启动workers上的可用executors资源
  • 调用_taskScheduler.start()方法,该方法会调用SparkDeploySchedulerBackend的start方法,该方法会使用appDescription启动executor子进程

组件间的交互

提交一个JOB

下面的图说明了在一个JOB提交的过程中涉及到的组件以及交互过程
这里写图片描述

Executor返回执行结果

下面的图说明了在一个JOB执行返回的过程
这里写图片描述

几个重要的调度规则

APP调度

默认情况下,用户向以Standalone模式运行的Spark集群提交的应用使用FIFO(先进先
出)的顺序进行调度。 每个应用会独占所有可用节点的资源。 用户可以通过配置参数
spark.cores.max决定一个应用可以在整个集群申请的CPU core数。 注意,这个参数不是控
制单节点可用多少核。 如果用户没有配置这个参数,则在Standalone模式下,默认每个应用
可以分配由参数spark.deploy.defaultCores决定的可用核数。

算法对应的类
- FIFO:FIFOSchedulingAlgorithm
- FAIR:FairSchedulingAlgorithm

JOB调度

Action 触发的JOB的本质就是调用了SparkContent 的runjob方法。JOB的调度在FIFO与FAIR的行为有点不同

FIFO

在默认情况下,Spark的调度器以FIFO(先进先出)方式调度Job的执行。 每个Job被切分为多个Stage。 第一个Job优先获取所有可用的资源,接下来第二个Job再获取剩余资源。 以此类推,如果第一个Job并没有占用所有的资源,则第二个Job还可以继续获取剩余资源,这样多个Job可以并行运行。 如果第一个Job很大,占用所有资源,则第二个Job就需要等待第一个任务执行完,释放空余资源,再申请和分配Job。

FAIR

在FAIR共享模式调度下,Spark在多Job之间以轮询(round robin)方式为任务分配资源,所有的任务拥有大致相当的优先级来共享集群的资源。
这就意味着当一个长任务正在执行时,短任务仍可以分配到资源,提交并执行,并且获得不错的响应时间。 这样就不用像以前一样需要等待长任务执行完才可以。 这种调度模式很适合多用户的场景。
用户可以通过配置spark.scheduler.mode方式来让应用以FAIR模式调度。
FAIR调度器同样支持将Job分组加入调度池中调度,用户可以同时针对不同优先级对每个调度池配置不同的调度权重
在默认情况下,每个调度池拥有相同的优先级来共享整个集群的资源,同样default pool中的每个Job也拥有同样优先级进行资源共享,但是在用户创建的每个资源池中,Job是通过FIFO方式进行调度的。

Stage调度

每个Stage对应的一个TaskSetManager,通过Stage回溯到最源头缺失的Stage提交到调度池pool中,在调度池中,
这些TaskSetMananger又会根据Job ID排序,先提交的Job的TaskSetManager优先调度,然
后一个Job内的TaskSetManager ID小的先调度,并且如果有未执行完的父母Stage的
TaskSetManager,则是不会提交到调度池中。

Task调度

当Stage不存在缺失的ParentStage时,会将其转换为TaskSet并提交。转换时依Stage类型进行转换:将ResultStage转换成ResultTask, ShuffleMapStage转换成ShuffleMapTask. Task个数由Stage中finalRDD 的分区数决定。

当转换成的TaskSet提交之后,将其通过taskScheduler包装成TaskSetManager并添加至调度队列中(Pool),等待调度。在包装成TaskSetManager时,根据task的preferredLocatitions将任务分类存放在pendingTasksForExecutor, pendingTaskForHost, pendingTasksForRack, pendingTaskWithNoPrefs及allPendingTasks中, 前三个列表是是包含关系(本地性越来越低),范围起来越大,例如:在pendingTasksForExecutor也在pendingTaskForHost,pendingTasksForRack中, 分类的目的是在调度时,依次由本地性高à低的查找task。

在进行Task调度时,首先根据调度策略将可调度所有taskset进行排序,然后对排好序的taskset待调度列表中的taskset,按序进行分配Executor。再分配Executor时,然后逐个为Executor列表中可用的Executor在此次选择的taskset中==按本地性由高到低查找适配任==务。此处任务调度为延迟调度,即若本次调度时间距上一任务结束时间小于当前本地性配制时间则等待,若过了配制时间,本地性要求逐渐降低,再去查找适配的task。当选定某一task后后将其加入runningtask列表,当其执行完成时会加入success列表,下次调度时就会过滤过存在这两个列表中的任务,避免重复调度。

当一个任务执行结束时,会将其从runningtask中移除,并加入success,并会适放其占用的执行资源,供后序task使用, 将判断其执行成功的task数与此taskset任务总数相等时,意为taskset中所有任务执行结束,也就是taskset结束。此时会将taskset移除出可调度队列。

重复上述过程直到taskset待调度列表为空。即所有作业(job)执行完成。

总结:整体的Task分发由TaskSchedulerImpl来实现,但是Task的调度(本质上是Task在哪个
分区执行)逻辑由TaskSetManager完成。 这个类监控整个任务的生命周期,当任务失败时
(如执行时间超过一定的阈值),重新调度,也会通过delay scheduling进行基于位置感知
(locality-aware)的任务调度

0 0
原创粉丝点击