4.master资源调度源码分析(Driver调度和Application调度(两种))

来源:互联网 发布:ps4串流pc软件 编辑:程序博客网 时间:2024/06/03 16:43
master资源调度中  , 分为Driver调度和Application调度
    1.Driver调度 :
        首先会对在master上注册过的worker进行随机打乱 , 利用Random.shuffle 方法就可以实现 , 同时将状态为ALIVE的worker放进一个hashmap 中 , 
        driver调度机制 , driver是程序员编写的程序 , 只有在yarn-cluster集群模式下才会被yarn调用 , 其余两种模式都是本地启动driver , 而不会注册并调度driver 
        若是yarn-cluster模式下则会遍历在yarn中所有缓存的driver , 同时遍历注册过的所有的worker , 若是worker的内存和cpu数都大于等于driver的内存和cpu数 , 则启动该driver , 并将该driver从缓存队列中清除 , 这也就说明了yarn-cluster模式下driver是运行在一个worker中的 ! 
        源码如下:
  1. /**
  2. * Schedule the currently available resources among waiting apps. This method will be called
  3. * every time a new app joins or resource availability changes.
  4. *
  5. * master的资源调度算法 , 分为平均资源调度算法和非平均资源调度算法
  6. */
  7. private def schedule() {
  8. // 首先检查master状态是否为ALIVE
  9. if (state != RecoveryState.ALIVE) { return }
  10. // First schedule drivers, they take strict precedence over applications
  11. // Randomization helps balance drivers
  12. //1.Driver调度 : 首先对传入的worker集合进行随机的打乱 , worker必须是ALIVE状态 , shuffledAliveWorkers为一数组,保存了所有活着的worker
  13. val shuffledAliveWorkers = Random.shuffle(workers.toSeq.filter(_.state == WorkerState.ALIVE))
  14. // 记录下状态为ALIVE的worker数量
  15. val numWorkersAlive = shuffledAliveWorkers.size
  16. // 初始化取worker的索引值 , 初始为0 , 最大值为numWorkersAlive
  17. var curPos = 0
  18. // 开始遍历所有等待的Driver , 其实只有在yarn-cluster模式下才会有driver的注册和调度
  19. // 而standalone和client模式都会在上传jar的worker机子上启动driver,没有注册和调度
  20. for (driver <- waitingDrivers.toList) { // iterate over a copy of waitingDrivers
  21. // We assign workers to each waiting driver in a round-robin fashion. For each driver, we
  22. // start from the last worker that was assigned a driver, and continue onwards until we have
  23. // explored all alive workers.
  24. // 标记
  25. var launched = false
  26. // 表示被拜访过的worker数量 , 该值肯定会小于numWorkersAlive
  27. var numWorkersVisited = 0
  28. // 对于每一个等待队列中的driver , 都需要遍历所有的worker , 检查worker的硬件资源是否符合driver的需求
  29. while (numWorkersVisited < numWorkersAlive && !launched) {
  30. // 获取一个状态为ALIVE的worker , curPos会每循环一次+1
  31. val worker = shuffledAliveWorkers(curPos)
  32. // 当前循环中被拜访的worker数量+1
  33. numWorkersVisited += 1
  34. //若是worker的内存大于等于driver所需要的内存 并且 worker可用的cpu core的数量大于等于dirver所需要的core
  35. if (worker.memoryFree >= driver.desc.mem && worker.coresFree >= driver.desc.cores) {
  36. // 将driver发布到资源符合的那台worker节点上进行启动
  37. launchDriver(worker, driver)
  38. // 该driver从等待队列中移除
  39. waitingDrivers -= driver
  40. // 当前遍历的driver通过 , 退出这次循环 , 进行下一个等待队列的driver
  41. launched = true
  42. }
  43. // 将worker的遍历序号+1
  44. curPos = (curPos + 1) % numWorkersAlive
  45. }
  46. }
上面的源码中只有lauchDriver方法需要看一下 , 如下 :
  1. /**
  2. * 将driver发布到对应的那台worker节点上去
  3. */
  4. def launchDriver(worker: WorkerInfo, driver: DriverInfo) {
  5. logInfo("Launching driver " + driver.id + " on worker " + worker.id)
  6. // 在worker节点中增加driver
  7. worker.addDriver(driver)
  8. // 给driver的worker赋值
  9. driver.worker = Some(worker)
  10. // 获取worker的actor代理对象 , 发送发布driver的消息
  11. worker.actor ! LaunchDriver(driver.id, driver.desc)
  12. // 更改driver的状态为RUNNING
  13. driver.state = DriverState.RUNNING
  14. }

    2.Application资源调度:
        有两种 , spreadOutApps(平均分配)调度算法和非spreadOutApps调度算法
        spreadOutApps(平均分配)调度算法:
        1.首先遍历缓存watingApps中需要调度的Application , 凡是注册过的Application都会存储在这个缓存中 , 并且过滤出还需要调度的core的Application , 然后从workers中遍历出状态为ALIVE且还有剩余cpu的worker , 并对其进行倒序排序;
        2.创建一个空数组 , 用来存储分配给每个worker的cpu数;
        3.计算出到底要给这个Application分配出多少个core , 原理是取app所需要的cpu数和所有worker中可用cpu数的最小值;
        4.遍历3中计算出来的值 , 为每一个worker分配cpu 当worker分配到一个cpu时该worker的cpu数加1 , 同时可用的cpu数减1, 直到分配完cpu为止 , 代码如下:
  1. //2.Application调度 : 默认的情况下采用平均资源调度算法 , spreadOutApps变量可以从SparkConf中设置,master会获取spreadOutApps = conf.getBoolean("spark.deploy.spreadOut", true)
  2. if (spreadOutApps) {
  3. // Try to spread out each app among all the nodes, until it has all its cores
  4. // 遍历所有的缓存队列的Application并且这个Application的还需要分配core
  5. for (app <- waitingApps if app.coresLeft > 0) {
  6. // 过滤出状态ALIVE并且能被Application使用的worker , 最后按照cpu core的数量进行倒序排序
  7. val usableWorkers = workers.toArray.filter(_.state == WorkerState.ALIVE)
  8. .filter(canUse(app, _)).sortBy(_.coresFree).reverse
  9. // numUsable记录 可用的worker数量
  10. val numUsable = usableWorkers.length
  11. // 创建一个空数组 , 用于记录每个worker被分配出去的core
  12. val assigned = new Array[Int](numUsable) // Number of cores to give on each node
  13. // 从当前Application需要的core的数量和所有worker可用的core数量取最小值 , 表示Application可以分配到的core数量
  14. var toAssign = math.min(app.coresLeft, usableWorkers.map(_.coresFree).sum)
  15. // 表示usableWorkers数组中的索引
  16. var pos = 0
  17. // 开始对当前Application需要每个worker节点分配多少core进行循环计算
  18. while (toAssign > 0) {
  19. // 如果当前的worker节点可用的core数量大于当前worker节点已经被分配出去的core的数量 ,那么继续分配1个core给当前的Application
  20. if (usableWorkers(pos).coresFree - assigned(pos) > 0) {
  21. // 当前Application需要被分配的core减1 , 直到为0 , 当为0的时候表示一个Application已经获取了所有的core
  22. toAssign -= 1
  23. // 将当前pos索引表示的worker节点被分配出去的core数量+1
  24. assigned(pos) += 1
  25. }
  26. // 分配下一个worker节点的core
  27. pos = (pos + 1) % numUsable
  28. }
     也就说 , 通过上面的算法 , 是将cpu数平均的分配到worker上了 , 可能就不是我们在用submit-shell提交App时指定的cpu数了 ; 
     5.在可用worker分配到cpu数时 , 那么就在worker启动executor , 并将executor添加到Applicaion的缓存结构中 , 代码如下:
  1. // Now that we've decided how many cores to give on each node, let's actually give them
  2. // 从第一个worker开始遍历 , assigned数组此时已经记录好了当前Application在每个worker上所需要的cpu core数量
  3. for (pos <- 0 until numUsable) {
  4. if (assigned(pos) > 0) {
  5. // 根据worker和当前worker已经分配的core数量创建一个executor
  6. val exec = app.addExecutor(usableWorkers(pos), assigned(pos))
  7. // 在当前executor上发表executor , 也就是创建executor
  8. launchExecutor(usableWorkers(pos), exec)
  9. // 将Application的状态更改为RUNNING
  10. app.state = ApplicationState.RUNNING
  11. }
  12. }
  13. }

    非spreadOutApps调度算法如下:
    该算符与spreadOutApps截然相反 , 是尽可能少的为这个Application启动worker , 也就说如果一个worker只有10个core可用 , 而Application需要20个core , 那么会将这个worker上的所有的core用完 , 代码如下:

  1. //非平均Application资源调度算法
  2. } else {
  3. // Pack each app into as few nodes as possible until we've assigned all its cores
  4. // 遍历每状态为ALIVE的worker , 并且检查worker的可用core数量是否大于0
  5. for (worker <- workers if worker.coresFree > 0 && worker.state == WorkerState.ALIVE) {
  6. // 遍历Application等待队列中的每一个Application并且判断其已经被分配的core数量是否大于0
  7. for (app <- waitingApps if app.coresLeft > 0) {
  8. // 判断一个Application是否可以在一个worker上运行 , 主要是判断Application所需要的每个节点的内存是否小于Worker节点剩余的内存 , 还要判断worker节点是否已经对这个Application启用了executor
  9. // 只有在worker节点内存容量通过并且没有该Application所对应的executor才能继续往下走
  10. if (canUse(app, worker)) {
  11. // 获取当前worker剩余的cpu core数量与当前Application需要的core数量的最小值
  12. val coresToUse = math.min(worker.coresFree, app.coresLeft)
  13. // 若最小值大于0
  14. if (coresToUse > 0) {
  15. // 在该worker节点上创建一个Application所对应的executor
  16. val exec = app.addExecutor(worker, coresToUse)
  17. // 在该worker节点上发布executor
  18. launchExecutor(worker, exec)
  19. // 更改当前Application的状态为RUNNING
  20. app.state = ApplicationState.RUNNING
  21. }
  22. }
  23. }
  24. }
  25. }

以上就是所有关于master资源调度的源码分析  , 其实源码比较难理解的就是Application的平均资源调度 , 但是道理却比较简单 !



















阅读全文
0 0
原创粉丝点击