大数据之Spark Executor资源分配

153 篇文章 19 订阅
43 篇文章 2 订阅

1 Driver 和 Executor 资源调度

当 Application 提交注册到 Master 后,Master 会返回 RegisteredApplication ,

之后便会调用 schedule()这个方法,来分配 Driver 的资源,和启动 Executor 的资源。

schedule() 方法是来调度当前可用资源的调度方法,它管理还在排队等待的 Apps 资源的分配,

这个方法是每次在集群资源发生变动的时候都会调用,根据当前集群最新的资源来进行 Apps 的资源分配。

Driver 资源调度:随机的将 Driver 分配到空闲的 Worker 上去,详细流程请看我写的注释 :

    // First schedule drivers, they take strict precedence over applications
    val shuffledWorkers = Random.shuffle(workers) // 把当前workers这个HashSet的顺序随机打乱
    for (worker <- shuffledWorkers if worker.state == WorkerState.ALIVE) { //遍历活着的workers
      for (driver <- waitingDrivers) { //在等待队列中的Driver们会进行资源分配
        if (worker.memoryFree >= driver.desc.mem && worker.coresFree >= driver.desc.cores) { //当前的worker内存和cpu均大于当前driver请求的mem和cpu,则启动
          launchDriver(worker, driver) //启动Driver 内部实现是发送启动Driver命令给指定Worker,Worker来启动Driver。
          waitingDrivers -= driver //把启动过的Driver从队列移除
        }
      }
    }

Executor 资源调度:Spark 默认提供了一种在各个节点进行 round-robin 的调度,用户可以自己设置这个 flag:

val spreadOutApps = conf.getBoolean("spark.deploy.spreadOut", true)

在介绍之前我们先介绍一个概念:

可用的 Worker:什么是可用?可用就是 资源空闲足够且满足一定的 规则来启动当前 App 的 Executor。

Spark 定义了一个 canUse 方法:这个方法接受一个 ApplicationInfo 的描述信息和当前 Worker 的描述信息。

  1. 当前 worker 的空闲内存比该 app 在每个 slave 要占用的内存 (executor.memory 默认512M) 大

  2. 当前 app 从未在此 worker 启动过 App

总结:从这点看出,要满足:该 Worker 的当前可用最小内存要比配置的 executor 内存大,并且对于同一个 App 只能在一个 Worker 里启动一个 Exeutor,

如果要启动第二个 Executor,那么请到其它 Worker 里。这样的才算是对 App 可用的 Worker

  /**
   * Can an app use the given worker? True if the worker has enough memory and we haven't already
   * launched an executor for the app on it (right now the standalone backend doesn't like having
   * two executors on the same worker).
   */
def canUse(app: ApplicationInfo, worker: WorkerInfo): Boolean = {
   worker.memoryFree >= app.desc.memoryPerSlave && !worker.hasExecutor(app)
}

SpreadOut 分配策略:

SpreadOut  分配策略是一种以 round-robin 方式遍历集群所有可用 Worker,分配 Worker 资源,

来启动创建 Executor 的策略,好处是尽可能的将 cores 分配到各个节点,最大化负载均衡和高并行。

下面看看,默认的 spreadOutApps 模式启动 App 的过程:

  1. 等待分配资源的 apps 队列默认是 FIFO 的。

  2. app.coresLeft 表示的是该 app 还有 cpu 资源没申请到:app.coresLeft  =  当前 app 申请的 maxcpus - granted 的cpus

  3. 遍历未分配完全的 apps,继续给它们分配资源

  4. usableWorkers  =   从当前 ALIVE 的 Workers 中过滤找出上文描述的可用 Worker,然后根据 cpus 的资源空闲,从大到小给 Workers 排序。

  5. 当 toAssign(即将要分配的的 core 数 > 0,就找到可以的 Worker 持续分配)

  6. 当可用 Worker 的 free cores 大于目前该 Worker 已经分配的 core 时,再给它分配 1 个 core,这样分配是很平均的方法。

  7. round-robin 轮询可用的 Worker 循环

  8. toAssign=0 时结束循环,开始根据分配策略去真正的启动 Executor。

举例: 1 个 APP 申请了 6 个 core, 现在有 2 个 Worker 可用。那么:toAssign  =  6,assigned  =  2

那么就会在 assigned(1) 和 assigned(0) 中轮询平均分配 cores,以 +1 core 的方式,

最终每个 Worker 分到3个 core,即每个 Worker 的启动一个Executor,每个 Executor 获得 3 个 cores。

// Right now this is a very simple FIFO scheduler. We keep trying to fit in the first app
// in the queue, then the second app, etc.
if (spreadOutApps) {
  // Try to spread out each app among all the nodes, until it has all its cores
  for (app <- waitingApps if app.coresLeft > 0) { //对还未被完全分配资源的apps处理
     val usableWorkers = workers.toArray.filter(_.state == WorkerState.ALIVE)
       .filter(canUse(app, _)).sortBy(_.coresFree).reverse //根据core Free对可用Worker进行降序排序。
     val numUsable = usableWorkers.length //可用worker的个数 eg:可用5个worker
     val assigned = new Array[Int](numUsable) //候选Worker,每个Worker一个下标,是一个数组,初始化默认都是0
     var toAssign = math.min(app.coresLeft, usableWorkers.map(_.coresFree).sum)//还要分配的cores = 集群中可用Worker的可用cores总和(10), 当前未分配core(5)中找最小的
     var pos = 0
     while (toAssign > 0) { 
       if (usableWorkers(pos).coresFree - assigned(pos) > 0) { //以round robin方式在所有可用Worker里判断当前worker空闲cpu是否大于当前数组已经分配core值
         toAssign -= 1
         assigned(pos) += 1 //当前下标pos的Worker分配1个core +1
       }
       pos = (pos + 1) % numUsable //round-robin轮询寻找有资源的Worker
     }
     // Now that we've decided how many cores to give on each node, let's actually give them
     for (pos <- 0 until numUsable) {
       if (assigned(pos) > 0) { //如果assigned数组中的值>0,将启动一个executor在,指定下标的机器上。
         val exec = app.addExecutor(usableWorkers(pos), assigned(pos)) //更新app里的Executor信息
         launchExecutor(usableWorkers(pos), exec)  //通知可用Worker去启动Executor
         app.state = ApplicationState.RUNNING
       }
     }
   }
 } else {

非 SpreadOut 分配策略:

非 SpreadOut 策略,该策略:会尽可能的根据每个 Worker 的剩余资源来启动 Executor,

这样启动的 Executor 可能只在集群的一小部分机器的 Worker 上。这样做对 node 较少的集群还可以,集群规模大了,Executor 的并行度和机器负载均衡就不能够保证了。

当用户设定了参数 spark.deploy.spreadOut 为  false 时,触发此分支 。

  1. 遍历可用 Workers

  2. 且遍历 Apps

  3. 比较当前 Worker 的可用 core 和 app 还需要分配的 core,取最小值当做还需要分配的 core

  4. 如果 coreToUse 大于 0,则直接拿可用的 core 来启动 Executor。奉献当前 Worker 全部资源。(Ps:挨个榨干每个Worker的剩余资源。。。。)

举例: App 申请 12 个 core,3 个 Worker,Worker1 剩余 1 个 core, Worke2 剩 7 个 core, Worker3 剩余 4 个 core.

这样会启动 3 个 Executor,Executor1 占用 1 个 core, Executor2 占用 7 个 core, Executor3 占用 4 个 core.

总结:这样是尽可能的满足 App,让其尽快执行,而忽略了其并行效率和负载均衡。

 } else {
      // Pack each app into as few nodes as possible until we've assigned all its cores
      for (worker <- workers if worker.coresFree > 0 && worker.state == WorkerState.ALIVE) {
        for (app <- waitingApps if app.coresLeft > 0) {
          if (canUse(app, worker)) { //直接问当前worker是有空闲的core
            val coresToUse = math.min(worker.coresFree, app.coresLeft) //有则取,不管多少
            if (coresToUse > 0) { //有
              val exec = app.addExecutor(worker, coresToUse) //直接启动
              launchExecutor(worker, exec)
              app.state = ApplicationState.RUNNING
            }
          }
        }
      }
    }
  }

2 总结:

  1. 在 Worker Actor中,每次 LaunchExecutor 会创建一个 CoarseGrainedExecutorBackend 进程,一个 Executor对应一个CoarseGrainedExecutorBackend

  2. 针对同一个 App,每个 Worker 里只能有一个针对该 App 的 Executor 存在,切记。如果想让整个 App 的 Executor 变多,设置SPARK_WORKER_INSTANCES,让 Worker 变多。

  3. Executor 的资源分配有2种策略:

    • SpreadOut :一种以 round-robin方式遍历集群所有可用 Worker,分配 Worker 资源,来启动创建 Executor 的策略,好处是尽可能的将 cores 分配到各个节点,最大化负载均衡和高并行。

    • 非 SpreadOut:会尽可能的根据每个 Worker 的剩余资源来启动 Executor,这样启动的 Executor 可能只在集群的一小部分机器的 Worker 上。这样做对 node 较少的集群还可以,集群规模大了,Executor 的并行度和机器负载均衡就不能够保证了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值