大数据IMF传奇行动绝密课程第36课:TaskScheduler内幕天机解密

来源:互联网 发布:张伯伦生涯数据 编辑:程序博客网 时间:2024/04/29 22:13

TaskScheduler内幕天机解密

1、TaskScheduler与SchedulerBackend
2、FIFO与FAIR两种调度模式彻底解密
3、Task数据本地性资源分配源码实现

一、通过spark-shell运行程序来观察TaskScheduler内幕
1、当我们启动spark-shell本身的时候,命令终端反馈回来的主要是ClientEndpoint和SparkDeploySchedulerBackend,这是因为此时还没有任何Job的触发,只是启动Application本身而已,所以主要就是实例化SparkContext并注册当前的应用程序给Master且从集群中获得ExecutorBackend计算资源;
2、DAGScheduler划分好Stage后会通过TaskSchedulerImpl中的TaskSetManager来管理当前要运行的Stage中的所有任务TaskSet,TaskSetManager会根据locality aware来为Task分配计算资源、监控Task的执行状态(例如重试、慢任务执行推测等)

二、TaskScheduler与SchedulerBackend
1、总体的底层任务调度的过程如下:
a)TaskSchedulerImpl.submitTasks主要的作用是将TaskSet加入到TaskSetManager中进行管理
b)schedulableBuilder.addTaskManager:schedulableBuilder会确定TaskSetManager的调度顺序,然后按照TaskSetManager的locality aware来确定每个Task具体运行在哪个ExecutorBackend中
c)CoarseGrainedSchedulerBackend.reviveOffers:给DriverEndpoint发送ReviveOffers消息
ReviveOffers本事是一个空的case object对象,只是起到触发底层资源调度的作用,在有Task提交或者计算资源变动的时候会发送ReviveOffers这个消息作为触发器;
d)在DriverEndpoint中路由接收ReviveOffers消息并路由到makeOffers具体的方法中;在makeOffers方法中首先准备好所有可以用于计算的workOffers(代表了所有可用ExecutorBackend中可以使用的Cores等信息)
e)TaskSchedulerImpl.resourceOffers:为每一个Task具体分配计算资源,输入是executorBackend及其上可用的Cores,输出TaskDescription的二维数组,在其中确定了每个Task具体运行在哪个ExecutorBackend;resourceOffers到底是如何确定Task具体运行在哪个ExecutorBackend上的呢?算法的实现具体如下:
i.通过Random.shuffle方法重新洗牌所有的计算资源以寻求计算的负载均衡;
ii.根据每个ExecutorBackend的Cores的个数声明类型为TaskDescription的ArrayBuffer数组
iii.如果有新的ExecutorBackend分配给我们的Job,此时会调用executorAdded来获得最新的完整的可用计算资源
iv.通过下述代码追求最高级别的优先级本地性:

for (taskSet <- sortedTaskSets; maxLocality <- taskSet.myLocalityLevels) {  do {    launchedTask = resourceOfferSingleTaskSet(        taskSet, maxLocality, shuffledOffers, availableCpus, tasks)  } while (launchedTask)}

v.通过调用TaskSetManager的resourceOffer最终确定每个Task具体运行在哪个ExecutorBackend的具体的Locality Level;
f)通过launchTasks把任务发送给ExecutorBackend去执行

补充:
1、Task默认的最大重试次数是4次;
def this(sc: SparkContext) = this(sc, sc.conf.getInt(“spark.task.maxFailures”, 4))
2、Spark应用程序目前支持两种调度器:FIFO、FAIR,可以通过spark-env.sh中spark.scheduler.mode进行具体的设置,默认情况下是FIFO的方式;

private val schedulingModeConf = conf.get("spark.scheduler.mode", "FIFO")val schedulingMode: SchedulingMode = try {SchedulingMode.withName(schedulingModeConf.toUpperCase)} catch {case e: java.util.NoSuchElementException =>  throw new SparkException(s"Unrecognized spark.scheduler.mode: $schedulingModeConf")}

3、TaskScheduler中要负责为Task分配计算资源;此时程序已经具备集群中的计算资源了,只不过我们要确定每个Task
,根据计算本地性原则确定Task具体要运行在哪个ExecutorBackend中;
4、TaskDescription中已经确定好了Task具体要运行在哪个ExecutorBackend上

private[spark] class TaskDescription(    val taskId: Long,    val attemptNumber: Int,    val executorId: String,    val name: String,    val index: Int,    // Index within this task's TaskSet    _serializedTask: ByteBuffer)  extends Serializable {

而确定Task具体运行在哪个ExecutorBackend上的算法是由TaskSchedulerBackend的resourceOffer方法决定
5、数据本地性从高到低依次为PROCESS_LOCAL, NODE_LOCAL, NO_PREF, RACK_LOCAL, ANY,其中NO_PREF是指机器本地性(一台机器可能有多个NODE,如虚拟化)
6、每个Task默认是采用一个线程进行计算的;

// CPUs to request per taskval CPUS_PER_TASK = conf.getInt("spark.task.cpus", 1)

7、DAGScheduler是从数据层面考虑preferredLocation的,而TaskScheduler是从具体计算Task的角度来考虑计算的本地性
8、Task进行广播时候的AkkaFrameSize的大小是128MB,如果任务大于等于128MB-200KB的话,则Task会直接被丢弃掉;如果小于的话会通过CoarseGrainedSchedulerBackend去launchTask到具体的ExecutorBackend上

0 0
原创粉丝点击