(七)Spark源码理解之TaskScheduler----part4

resourceOffers():该方法是TaskSchedulerImpl的核心所在,实现将任务指定给对应的从节点中的executor,其主要思路可以概述为:

首先将获取的每个executor的资源,组成组成WorkerOffer序列,然后将其打乱,接着对每个任务集进行操作,首先判断第一个executor,如果它的CPU个数符合任务集中的任务所需,则将该任务集中挂在该executor下的任务取出,得到相应的任务描述,然后添加到任务描述的容器中,之后对第二个executor进行同样的操作。具体可以查看代码详解

讲述该代码之前简要理一下思路:


WorkerOffer指的就是每个executor上可用的资源,它有几个参数:executorId,host,CPU的个数

下面讲述代码:

def resourceOffers(offers: Seq[WorkerOffer]): Seq[Seq[TaskDescription]] = synchronized {
    SparkEnv.set(sc.env)
    // Mark each slave as alive and remember its hostname
    for (o <- offers) {//将executor和host的关系记录在相应的容器中
      executorIdToHost(o.executorId) = o.host
      if (!executorsByHost.contains(o.host)) {
        executorsByHost(o.host) = new HashSet[String]()
        executorAdded(o.executorId, o.host)//调用了DAGScheduler的方法
      }
    }
    // Randomly shuffle offers to avoid always placing tasks on the same set of workers.
    val shuffledOffers = Random.shuffle(offers)//将序列WorkerOffer的内容打乱,避免任务总是分配给重复的几个从节点的executor
// Build a list of tasks to assign to each worker.
//为从节点的executor建立一个TaskDescription类型的ArrayBuffer,目的在于存储分配给该executor的任务
    val tasks = shuffledOffers.map(o => new ArrayBuffer[TaskDescription](o.cores))
    val availableCpus = shuffledOffers.map(o => o.cores).toArray//将各个executor的CPU的个数单独取出来,组成一个数组
    val sortedTaskSets = rootPool.getSortedTaskSetQueue//从线程池中取出以及排好序的任务集
    for (taskSet <- sortedTaskSets) {
      logDebug("parentName: %s, name: %s, runningTasks: %s".format(
        taskSet.parent.name, taskSet.name, taskSet.runningTasks))
    }

    // Take each TaskSet in our scheduling order, and then offer it each node in increasing order
    // of locality levels so that it gets a chance to launch local tasks on all of them.
    var launchedTask = false
    for (taskSet <- sortedTaskSets; maxLocality <- TaskLocality.values) {
      do {
        launchedTask = false//设置装在任务的标志位假
        for (i <- 0 until shuffledOffers.size) {//逐个逐个给每个从节点的executor分配任务,如若某executor的CPU个数不满足任务所需的CPU时则轮到下一个executor
          val execId = shuffledOffers(i).executorId//获取executor的id
          val host = shuffledOffers(i).host//获取executor的host
          if (availableCpus(i) >= CPUS_PER_TASK) {//如果该executor的CPU个数满足任务所需的最大CPU的个数,则可以分配,否则,就将任务分配给下一个executor
            for (task <- taskSet.resourceOffer(execId, host, maxLocality)) {//调用TaskSetManager的resourceOffer()方法,获得任务的任务描述
              tasks(i) += task//将任务描述添加到该executor的任务描述容器中
              val tid = task.taskId
              taskIdToTaskSetId(tid) = taskSet.taskSet.id//将taskId和TaskSetId对应起来
              taskIdToExecutorId(tid) = execId//将taskId和executorId对应起来
              activeExecutorIds += execId
              executorsByHost(host) += execId
              availableCpus(i) -= CPUS_PER_TASK//将该executor的CPU个数减少
              assert (availableCpus(i) >= 0)
              launchedTask = true
            }
          }
        }
      } while (launchedTask)
    }

    if (tasks.size > 0) {
      hasLaunchedTask = true
    }
    return tasks//返回最终结果
  }

3.1 Schedulable

Schedulable类其实就是可调度对象,具有两种类型:线程池和TaskSetManager任务集管理器,可以联想到上面我讲过的任务是由线程去执行的。

该类中主要就是定义了添加,移除可调度对象等方法

3.2 SchedulableBuilder

SchedulableBuilder的作用是定义一颗可调度对象的树,实际上可以理解为用来管理线程池和任务集管理器的,它有个最重要的方法就是:addTaskSetManager(manager:Schedulable, properties: Properties)

实现在线程池中添加TaskSetManager

3.3 SchedulerBackend

SchedulerBackend可以认为是TaskSchedulerImpl实现的后台支撑,为它提供各种调度服务。其中最主要的方法就是reviveOffers(),这个方法可以理解为重新生成相应executor的WorkerOffer,然后分配任务给executor,获得指定给该executor的任务描述,最后executor装载该任务,在TaskSchedulerImpl的多个方法中都调用了此方法,此外SchedulerBackend在不同模式下有对应的实现方式,实质就是其有不同子类。

未完待续。。。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值