资源调度(学习笔记)

文章目录

  • 资源调度流程
  • 资源调度原理
    • workers集合
    • waitingDriver集合
    • waitingApps集合
    • Schedule方法
  • 资源调度源码分析
    • 资源调度三大集合源码解析
    • 一 schedule方法
      • 1.1 Random.shuffle是一个java的方法。
      • 1.2 launchDriver 发起driver
    • 二 startExecutorsOnWorkers 在worker上启动executor进程
    • 三 scheduleExecutorsOnWorkers在每一个worker上调度资源
    • 四 allocateWorkerResourceToExecutors在worker上分配具体的资源

资源调度流程

1,当集群启动时,各个worker向master汇报信息,封装到workerInfo类中,放入workers集合。
2,当客户端向集群提交Application时,首先会在客户端启动一个sparksubmit进程
3,为driver向Master申请资源,此时先遍历waitingDriver集合,集合不为空,说明有客户端向master申请资源,此时再遍历workers集合,随机找一台资源充沛的worker节点,启动driver进程。
4,Driver启动成功后,把这个申请信息从waitingDriver中删除掉。
5,Driver向Master为当前的Application申请资源。此时将请求信息封装到ApplicationInfo中,放入waitingApps集合中。
5,Master接收到请求后,查看waitingApps集合,若不为空,再遍历worker集合,寻找符合条件的worker节点,在这些worker节点上启动一批excuter进程,这些excuter,默认占用1G内存和这个excuter所 能管理的所有的核。
7,excuter启动成功后,向TaskSchedule反向注册。
8,TaskSchedule可以向各个excuter进程分发task。

资源调度(学习笔记)_第1张图片

资源调度原理

workers集合

当集群启动的时候,各个worker节点向master汇报信息,这些信息先是封装到workerInfo类中,再把这些类放到workers集合中。

waitingDriver集合

waitingDriver存放的都是等待client向master申请资源的信息(这个资源实际上就是Driver)。
当 waitingDriver集合中元素不为空,说明有客户端向master申请资源,此时应该查看当前集群的资源情况(查看一下workers集合),找到符合要求的节点,启动Driver,当Driver成功启动,这个申请资源的信息从waitingDrivers中删掉。

waitingApps集合

waitingApps存放的都是Driver向Master申请的资源(为当前Application申请的资源)。
当waitingApps集合不为空,说明Driver向Master为当前的Application申请资源,查看集群的资源情况(workers集合),找到合适的worker节点,启动Excuter进程,默认情况下,每一个worker为当前的Application只是启动一个Excutor,这个Excutor会使用1G内存和这个worker所管理的所有的core。

Schedule方法

waitingApps和waitingDriver这两个集合一直在发生变化,所以需要时时监控他们的状态。

所以Master里有一个schedule()方法,每当这两个集合中添加元素的时候,就会反调这个方法,这个方法里有2套逻辑,分别对应这两个集合,当某个集合反调这个函数时,它会按照上述处理过程来处理。

资源调度源码分析

Master是通过schedule方法进行资源调度,告知worker启动executor等。

资源调度三大集合源码解析

val workers = new HashSet[WorkerInfo]   // 存储每一个Worker节点的基本信息
val waitingApps = new ArrayBuffer[ApplicationInfo]
private val waitingDrivers = new ArrayBuffer[DriverInfo]

WorkerInfo

  • host:Worker所在的节点
  • port:端口号
  • cores:worker所有的核数
  • memory:它所有的内存
  • endpoint:spark内部通信属性,类似于邮箱
  • webUiAddress:外部UI的地址,默认端口8081

DriverInfo

  • StartTime:启动时间
  • id:id号
  • desc:Driver的资源描述信息

ApplicationInfo

  • startTime:开启时间
  • id:id号
  • desc:App的使用资源信息

一 schedule方法

前面分析的都是怎样将资源,如worker、executor、Application等加入到各自的等待队列中(失败 完成 异常等等).
在等待的应用程序中调度当前可用的资源。
此方法将被调用–>每次一个新的应用程序 连接 或可用资源改变的时候。

Master上面最重要的部分–>Master资源调度算法(其实就是在worker上面启动Executor)

  • 1 判断master状态,只有alive状态的master才可以进行资源调度,standby是不能够调度的
  • 2 将可用的worker节点打乱,这样有利于driver的负载均衡
  • 3 进行driver资源调度,遍历处于等待状态的driver队列,发起driver
  • 4 在worker上开启executor进程
private def schedule(): Unit = {
// 判断Master的状态
// 只有alive状态的master才可以进行资源调度,standby是不能够调度的
if (state != RecoveryState.ALIVE) { return }
 
// 将可用的worker节点打乱,这样有利于driver的均衡
val shuffledWorkers = Random.shuffle(workers)
for (worker <- shuffledWorkers if worker.state == WorkerState.ALIVE) {
// 进行driver资源调度,遍历处于等待状态的driver队列
for (driver <- waitingDrivers) {
// 判断worker的可使用内存是否大于driver所需要的内存以及worker可使用cpu核数是否大于driver所需要的cpu核数
if (worker.memoryFree >= driver.desc.mem && worker.coresFree >= driver.desc.cores) {
// 满足条件发起driver
launchDriver(worker, driver)
// 将当前driver从等待队列中移除
waitingDrivers -= driver
}
}
}
// 在worker上开启executor进程
startExecutorsOnWorkers()
}

1.1 Random.shuffle是一个java的方法。

Random.shuffle的原理是:
对传入集合中的元素进行随机的打乱,取出workers中所有注册上来的worker,首先进行过滤,必须保证(作为参数)传入的worker的状态都是alive的,然后, 对于状态为alive的worker,调用shuffle方法进行打乱。

将worker存入到ArrayBuffer中并赋值给buf.
swap函数: 是将索引位置上的Worker两两进行交换.
For循环: 从buf中最后一个元素开始循环,一直到索引为3,其中的nextInt是取0到n-1的随机数,然后调用swp()函数,将n-1和k进行交换,这样执行结束后,buf中的Worker顺序完全被打乱了

def shuffle[T, CC[X] <: TraversableOnce[X]](xs: CC[T])(implicit bf: CanBuildFrom[CC[T], T, CC[T]]): CC[T] = {
val buf = new ArrayBuffer[T] ++= xs
 
def swap(i1: Int, i2: Int) {
val tmp = buf(i1)
buf(i1) = buf(i2)
buf(i2) = tmp
}
 
for (n <- buf.length to 2 by -1) {
val k = nextInt(n)
swap(n - 1, k)
}
 
(bf(xs) ++= buf).result()
}

1.2 launchDriver 发起driver

首先调度driver–>优先级高于Application 为什么先调度driver?
其实,只有提交方式为cluster的时候,才会注册driver,然后调度driver
standalone和client都是在本地启动driver,而不会来注册driver,更不用说调度driver了


/**
* 判断某个worker上面有driver所需的足够资源来启动相应的Executor
* @param worker
* @param driver
*/
private def launchDriver(worker: WorkerInfo, driver: DriverInfo) {
// 提示信息
logInfo("Launching driver " + driver.id + " on worker " + worker.id)
// 将driver的信息加入到要为其启动Executor的workerInfo中
worker.addDriver(driver)
driver.worker = Some(worker)
// 向worker发送LaunchDriver消息
worker.endpoint.send(LaunchDriver(driver.id, driver.desc))
// 将driver的状态修改为RUNNING
driver.state = DriverState.RUNNING
}

二 startExecutorsOnWorkers 在worker上启动executor进程

/**
* Schedule and launch executors on workers
* 在worker上开启executor进程
*/
private def startExecutorsOnWorkers(): Unit = {
// 遍历处于等待状态的application,且处于等待的状态的application的所需要的cpu核数大于0
// coresLeft = app请求的核数-已经分配给executor的核数的和
for (app <- waitingApps if app.coresLeft > 0) {
// 每一个executor所需要的核数
val coresPerExecutor: Option[Int] = app.desc.coresPerExecutor
// 过滤出有效的可用worker
// 再从worker中过滤出worker剩余内存和CPU核数不小于app对应executor所需要的内存和CPU核数
// 按照剩余的CPU核数反向排序woker
val usableWorkers = workers.toArray.filter(_.state == WorkerState.ALIVE)
.filter(worker => worker.memoryFree >= app.desc.memoryPerExecutorMB &&
worker.coresFree >= coresPerExecutor.getOrElse(1))
.sortBy(_.coresFree).reverse
 
// 在可用的worker上调度executor,启动executor有两种算法模式:
// 一:将应用程序尽可能多的分配到不同的worker上:spreadOutApps(平均分配)
// 二:和第一种相反,分配到尽可能少的worker上,通常用于计算密集型;非spreadOutApps(有点按需分配的意思)
// 每一个executor所需要的核数是可以配置的,一般来讲如果worker有足够的内存和CPU核数,同一个应用程序就可以
// 在该worker启动多个executors;否则就不能再启动新的executor了,则需要到其他worker上去分配executor了
val assignedCores = scheduleExecutorsOnWorkers(app, usableWorkers, spreadOutApps)
 
// 在可用的worker上分配资源给executor
for (pos <- 0 until usableWorkers.length if assignedCores(pos) > 0) {
allocateWorkerResourceToExecutors(
app, assignedCores(pos), coresPerExecutor, usableWorkers(pos))
}
}
}

三 scheduleExecutorsOnWorkers在每一个worker上调度资源

判断该worker能不能分配一个或者多个executor,能则分配相对应的executor所需要的CPU核数.

private def scheduleExecutorsOnWorkers(
      app: ApplicationInfo,
      usableWorkers: Array[WorkerInfo],
      spreadOutApps: Boolean): Array[Int] = {
    // 每一个Executor所需要的核数,若没有设置则为null
    val coresPerExecutor = app.desc.coresPerExecutor
    // 加入为空,给他设置默认最小值1
    val minCoresPerExecutor = coresPerExecutor.getOrElse(1)
    val oneExecutorPerWorker = coresPerExecutor.isEmpty
    // 每一个Executor所需要的内存
    val memoryPerExecutor = app.desc.memoryPerExecutorMB
    // 可用的Worker个数
    val numUsable = usableWorkers.length
    val assignedCores = new Array[Int](numUsable) // 每一个Worker可以贡献的核
    val assignedExecutors = new Array[Int](numUsable) // 每一个Worker启动的executor的个数
    // 计算所有可用worker的可用核数
    var coresToAssign = math.min(app.coresLeft, usableWorkers.map(_.coresFree).sum)

    /** Return whether the specified worker can launch an executor for this app. */
    def canLaunchExecutor(pos: Int): Boolean = {
      val keepScheduling = coresToAssign >= minCoresPerExecutor
      val enoughCores = usableWorkers(pos).coresFree - assignedCores(pos) >= minCoresPerExecutor

      // If we allow multiple executors per worker, then we can always launch new executors.
      // Otherwise, if there is already an executor on this worker, just give it more cores.
      val launchingNewExecutor = !oneExecutorPerWorker || assignedExecutors(pos) == 0
      if (launchingNewExecutor) {
        val assignedMemory = assignedExecutors(pos) * memoryPerExecutor
        val enoughMemory = usableWorkers(pos).memoryFree - assignedMemory >= memoryPerExecutor
        val underLimit = assignedExecutors.sum + app.executors.size < app.executorLimit
        keepScheduling && enoughCores && enoughMemory && underLimit
      } else {
        // We're adding cores to an existing executor, so no need
        // to check memory and executor limits
        keepScheduling && enoughCores
      }
    }

    // Keep launching executors until no more workers can accommodate any
    // more executors, or if we have reached this application's limits
    var freeWorkers = (0 until numUsable).filter(canLaunchExecutor)
    while (freeWorkers.nonEmpty) {
      freeWorkers.foreach { pos =>
        var keepScheduling = true
        while (keepScheduling && canLaunchExecutor(pos)) {
          coresToAssign -= minCoresPerExecutor
          assignedCores(pos) += minCoresPerExecutor

          // If we are launching one executor per worker, then every iteration assigns 1 core
          // to the executor. Otherwise, every iteration assigns cores to a new executor.
          if (oneExecutorPerWorker) {
            assignedExecutors(pos) = 1
          } else {
            assignedExecutors(pos) += 1
          }

          // Spreading out an application means spreading out its executors across as
          // many workers as possible. If we are not spreading out, then we should keep
          // scheduling executors on this worker until we use all of its resources.
          // Otherwise, just move on to the next worker.
          if (spreadOutApps) {
            keepScheduling = false
          }
        }
      }
      freeWorkers = freeWorkers.filter(canLaunchExecutor)
    }
    assignedCores
  }

四 allocateWorkerResourceToExecutors在worker上分配具体的资源

private def allocateWorkerResourceToExecutors(
app: ApplicationInfo,
assignedCores: Int,
coresPerExecutor: Option[Int],
worker: WorkerInfo): Unit = {
 
// 获取该worker应该有多少个executor
val numExecutors = coresPerExecutor.map { assignedCores / _ }.getOrElse(1)
// 获取每一个executor应该分配的核数,如果没有指定则使用计算的应该分配的核数
val coresToAssign = coresPerExecutor.getOrElse(assignedCores)
for (i <- 1 to numExecutors) {
// 向worker上添加executor,创建ExecutorDesc对象,更新application已经分配到的cpu核数
val exec = app.addExecutor(worker, coresToAssign)
// 启动executor
launchExecutor(worker, exec)
// 更新application的状态
app.state = ApplicationState.RUNNING
}
}

五 launchExecutor发起executor

/**
* launchExecutor发起executor
* @param worker-->WorkerInfo
* @param exec-->ExecutorDesc
*/
private def launchExecutor(worker: WorkerInfo, exec: ExecutorDesc): Unit = {
logInfo("Launching executor " + exec.fullId + " on worker " + worker.id)
// worker启动executor,并且更新worker的cpu和内存信息
worker.addExecutor(exec)
worker.endpoint.send(LaunchExecutor(masterUrl,
exec.application.id, exec.id, exec.application.desc, exec.cores, exec.memory))
// 向application发送ExecutorAdded消息
exec.application.driver.send(
ExecutorAdded(exec.id, worker.id, worker.hostPort, exec.cores, exec.memory))
}

你可能感兴趣的:(大数据学习笔记)