TaskScheduler源码解析

在DAGScheduler中创建好stage后,针对stage的task,创建TaskSet对象,调用TaskScheduler的submitTasks()方法,提交TaskSet,默认情况下我们的standalone模式使用的是TaskSchedulerImpl

taskScheduler.submitTasks(new TaskSet(
        tasks.toArray, stage.id, stage.latestInfo.attemptId, jobId, properties))

点击进入submitTasks,首先先执行如下语句

val manager = createTaskSetManager(taskSet, maxTaskFailures)

给每一个TaskSet,都会创建一个TaskSetManager , 它会负责它的那个TaskSet的任务执行状况的监视和管理

stageTaskSets(taskSet.stageAttemptId) = manager

然后加入内存缓存中

backend.reviveOffers()

SparkContext 原理剖析的时候,创建TaskScheduler的时候,一件非常重要的事情,就是为TaskSchedulerImpl创建一个SparkDeploySchedulerBackend,这里的backend就是之前创建好的SparkDeploySchedulerBackend , 而且这个backend是负责创建AppClient,向Master注册application的,随后点击进入reviveOffers

  override def reviveOffers() {
    driverEndpoint.send(ReviveOffers)
  }
case ReviveOffers =>
   makeOffers()
    private def makeOffers() {
      // Filter out executors under killing
      // 第一步 调用TaskSchedulerImpl的resourceOffers()方法,执行任务分配算法,将各个task分配到executor上去
      // 第二步 分配好task到executor之后,执行自己的launchTask,将分配的task发送LauncherTask消息
      // 到对应的executor上去,有executor启动并执行task
      // resourceOffers分配算法
      val activeExecutors = executorDataMap.filterKeys(executorIsAlive)
      val workOffers = activeExecutors.map { case (id, executorData) =>
        new WorkerOffer(id, executorData.executorHost, executorData.freeCores)
      }.toSeq
      launchTasks(scheduler.resourceOffers(workOffers))
    }

其中比较重要的是resourceOffers,它是task的分配算法,点击进入其中

  def resourceOffers(offers: Seq[WorkerOffer]): Seq[Seq[TaskDescription]] = synchronized {
    // Mark each slave as alive and remember its hostname
    // Also track if new executor is added
    var newExecAvail = false
    for (o <- offers) {
      executorIdToHost(o.executorId) = o.host
      executorIdToTaskCount.getOrElseUpdate(o.executorId, 0)
      if (!executorsByHost.contains(o.host)) {
        executorsByHost(o.host) = new HashSet[String]()
        executorAdded(o.executorId, o.host)
        newExecAvail = true
      }
      for (rack <- getRackForHost(o.host)) {
        hostsByRack.getOrElseUpdate(rack, new HashSet[String]()) += o.host
      }
    }

    // Randomly shuffle offers to avoid always placing tasks on the same set of workers.
    // 将可用的executor进行shuffle,也就是说,进行打散,尽量可以进行负责均衡
    val shuffledOffers = Random.shuffle(offers)
    // Build a list of tasks to assign to each worker.
    // 然后针对WorkOffer,创建一堆需要用的东西
    // 比如Tasks,可以理解为一个二维数组,ArrayBuffer,元素又是一个ArrayBuffer
    // 并且每个子ArrayBuffer的数量是固定的,也就是这个executor可用的cpu数量
    val tasks = shuffledOffers.map(o => new ArrayBuffer[TaskDescription](o.cores))
    val availableCpus = shuffledOffers.map(o => o.cores).toArray

    // 从roolPool中取出了排序的TaskSet
    // 之前讲解TaskScheduler初始化的时候,我们知道,创建完TaskSchedulerImpl、SparkDeploySchedulerBackend之后
    // 执行一个initialize() 方法,在这个方法中,其实会创建一个调度池
    // 这里相当于说,所有提交的TaskSet,首先,会放入调度池
    // 然后再执行task分配算法的时候,会从这个调度池中取出拍好队的TaskSet
    val sortedTaskSets = rootPool.getSortedTaskSetQueue
    for (taskSet <- sortedTaskSets) {
      logDebug("parentName: %s, name: %s, runningTasks: %s".format(
        taskSet.parent.name, taskSet.name, taskSet.runningTasks))
      if (newExecAvail) {
        taskSet.executorAdded()
      }
    }

    // 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.
    // NOTE: the preferredLocality order: PROCESS_LOCAL, NODE_LOCAL, NO_PREF, RACK_LOCAL, ANY

    // 任务分配算法的核心
    // 双重for循环,遍历所有的taskset,以及每一种本地化级别

    // 本地化级别:
    // PROCESS_LOCAL -> 进程本地化,rdd的partition和task进入一个executor内,那么速度当然快
    // NODE_LOCAL -> rdd的partition和task,不在一个executor中,不在一个进程中,但是在一个worker节点上
    // NO_PREF -> 无,没有所谓的本地化级别
    // ROCK_LOCAL -> 机架本地化,至少rdd的partition和task在同一个机架上
    // ANY -> 任意的本地化级别

    // 对每个taskset,从最好的一种本地化级别遍历
    //
    var launchedTask = false
    for (taskSet <- sortedTaskSets; maxLocality <- taskSet.myLocalityLevels) {
      do {
        // 对当前taskset
        // 尝试优先使用最小的本地化级别,将taskset的task,在executor上进行启动
        // 如果启动不了,那么就跳出这个do while循环,进入下一种本地化级别,也就是放大本地化级别
        // 以此类推,知道尝试将taskset在某些本地化级别,让task在executor上全部启动
        launchedTask = resourceOfferSingleTaskSet(
            taskSet, maxLocality, shuffledOffers, availableCpus, tasks)
      } while (launchedTask)
    }

    if (tasks.size > 0) {
      hasLaunchedTask = true
    }
    return tasks
  }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值