Spark調度模式-FIFO和FAIR

Spark中的調度模式主要有兩種:FIFO和FAIR。默認情況下Spark的調度模式是FIFO(先進先出),誰先提交誰先執行,後面的任務需要等待前面的任務執行。而FAIR(公平調度)模式支持在調度池中爲任務進行分組,不同的調度池權重不同,任務可以按照權重來決定執行順序。對這兩種調度模式的具體實現,接下來會根據spark-1.6.0的源碼來進行詳細的分析。使用哪種調度器由參數spark.scheduler.mode來設置,可選的參數有FAIR和FIFO,默認是FIFO。

一、源碼入口

  在Scheduler模塊中,當Stage劃分好,然後提交Task的過程中,會進入TaskSchedulerImpl#submitTasks方法。

schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties)   //目前支持FIFO和FAIR兩種調度策略
  • 1
  • 1

  在上面代碼中有一個schedulableBuilder對象,這個對象在TaskSchedulerImpl類中的定義及實現可以參考下面這段源代碼:

var schedulableBuilder: SchedulableBuilder = null
...
  def initialize(backend: SchedulerBackend) {
    this.backend = backend
    // temporarily set rootPool name to empty
    rootPool = new Pool("", schedulingMode, 0, 0)
    schedulableBuilder = {
      schedulingMode match {
        case SchedulingMode.FIFO =>
          new FIFOSchedulableBuilder(rootPool)  //rootPool包含了一組TaskSetManager
        case SchedulingMode.FAIR =>
          new FairSchedulableBuilder(rootPool, conf)  //rootPool包含了一組Pool樹,這棵樹的葉子節點都是TaskSetManager
      }
    }
    schedulableBuilder.buildPools() //在FIFO中的實現是空
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

  根據用戶配置的SchedulingMode決定是生成FIFOSchedulableBuilder還是生成FairSchedulableBuilder類型的schedulableBuilder對象。 
  SchedulableBuilder繼承關係
  在生成schedulableBuilder後,調用其buildPools方法生成調度池。 
  調度模式由配置參數spark.scheduler.mode(默認值爲FIFO)來確定。 
  兩種模式的調度邏輯圖如下: 
  調度模式邏輯圖

二、FIFOSchedulableBuilder

  FIFO的rootPool包含一組TaskSetManager。從上面的類繼承圖中看出在FIFOSchedulableBuilder中有兩個方法:

1、buildPools

  實現爲空

override def buildPools() {
    // nothing
  }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

所以,對於FIFO模式,獲取到schedulableBuilder對象後,在調用buildPools方法後,不做任何操作。

2、addTaskSetManager

  該方法將TaskSetManager裝載到rootPool中。直接調用的方法是Pool#addSchedulable()。

  override def addTaskSetManager(manager: Schedulable, properties: Properties) {
    rootPool.addSchedulable(manager)
  }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

Pool#addSchedulable()方法:

val schedulableQueue = new ConcurrentLinkedQueue[Schedulable]
...
  override def addSchedulable(schedulable: Schedulable) {
    require(schedulable != null)
    schedulableQueue.add(schedulable)
    schedulableNameToSchedulable.put(schedulable.name, schedulable)
    schedulable.parent = this
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

將該TaskSetManager加入到調度隊列schedulableQueue中。

三、FairSchedulableBuilder

  FAIR的rootPool中包含一組Pool,在Pool中包含了TaskSetManager。

1、buildPools

  在該方法中,會讀取配置文件,按照配置文件中的配置參數調用buildFairSchedulerPool生成配置的調度池,以及調用buildDefaultPool生成默認調度池。 
  默認情況下FAIR模式的配置文件是位於SPARK_HOME/conf/fairscheduler.xml文件,也可以通過參數spark.scheduler.allocation.file設置用戶自定義配置文件。 
spark中提供的fairscheduler.xml模板如下所示:

<allocations>
  <pool name="production">
    <schedulingMode>FAIR</schedulingMode>
    <weight>1</weight>
    <minShare>2</minShare>
  </pool>
  <pool name="test">
    <schedulingMode>FIFO</schedulingMode>
    <weight>2</weight>
    <minShare>3</minShare>
  </pool>
</allocations>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

參數含義: 
(1)name: 該調度池的名稱,可根據該參數使用指定pool,入sc.setLocalProperty("spark.scheduler.pool", "test") 
(2)weight: 該調度池的權重,各調度池根據該參數分配系統資源。每個調度池得到的資源數爲weight / sum(weight),weight爲2的分配到的資源爲weight爲1的兩倍。 
(3)minShare: 該調度池需要的最小資源數(CPU核數)。fair調度器首先會嘗試爲每個調度池分配最少minShare資源,然後剩餘資源纔會按照weight大小繼續分配。 
(4)schedulingMode: 該調度池內的調度模式。

2、buildFairSchedulerPool

  從上面的配置文件可以看到,每一個調度池有一個name屬性指定名字,然後在該pool中可以設置其schedulingMode(可爲空,默認爲FIFO), weight(可爲空,默認值是1), 以及minShare(可爲空,默認值是0)參數。然後使用這些參數生成一個Pool對象,把該pool對象放入rootPool中。入下所示:

val pool = new Pool(poolName, schedulingMode, minShare, weight)
rootPool.addSchedulable(pool)
  • 1
  • 2
  • 1
  • 2

3、buildDefaultPool

  如果如果配置文件中沒有設置一個name爲default的pool,系統纔會自動生成一個使用默認參數生成的pool對象。各項參數的默認值在buildFairSchedulerPool中有提到。

4、addTaskSetManager

  這一段邏輯中是把配置文件中的pool,或者default pool放入rootPool中,然後把TaskSetManager存入rootPool對應的子pool。

  override def addTaskSetManager(manager: Schedulable, properties: Properties) {
    var poolName = DEFAULT_POOL_NAME
    var parentPool = rootPool.getSchedulableByName(poolName)
    if (properties != null) {
      poolName = properties.getProperty(FAIR_SCHEDULER_PROPERTIES, DEFAULT_POOL_NAME)
      parentPool = rootPool.getSchedulableByName(poolName)
      if (parentPool == null) {
        // we will create a new pool that user has configured in app
        // instead of being defined in xml file
        parentPool = new Pool(poolName, DEFAULT_SCHEDULING_MODE,
          DEFAULT_MINIMUM_SHARE, DEFAULT_WEIGHT)
        rootPool.addSchedulable(parentPool)
        logInfo("Created pool %s, schedulingMode: %s, minShare: %d, weight: %d".format(
          poolName, DEFAULT_SCHEDULING_MODE, DEFAULT_MINIMUM_SHARE, DEFAULT_WEIGHT))
      }
    }
    parentPool.addSchedulable(manager)
    logInfo("Added task set " + manager.name + " tasks to pool " + poolName)
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

5、FAIR調度池使用方法

  在Spark-1.6.1官方文檔中寫道:

如果不加設置,jobs會提交到default調度池中。由於調度池的使用是Thread級別的,只能通過具體的SparkContext來設置local屬性(即無法在配置文件中通過參數spark.scheduler.pool來設置,因爲配置文件中的參數會被加載到SparkConf對象中)。所以需要使用指定調度池的話,需要在具體代碼中通過SparkContext對象sc來按照如下方法進行設置: 
sc.setLocalProperty("spark.scheduler.pool", "test") 
設置該參數後,在該thread中提交的所有job都會提交到test Pool中。 
如果接下來不再需要使用到該test調度池, 
sc.setLocalProperty("spark.scheduler.pool", null)

四、FIFO和FAIR的調度順序

  這裏必須提到的一個類是上面提到的Pool,在這個類中實現了不同調度模式的調度算法

  var taskSetSchedulingAlgorithm: SchedulingAlgorithm = {
    schedulingMode match {
      case SchedulingMode.FAIR =>
        new FairSchedulingAlgorithm()
      case SchedulingMode.FIFO =>
        new FIFOSchedulingAlgorithm()
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

FIFO模式的算法類是FIFOSchedulingAlgorithm,FAIR模式的算法實現類是FairSchedulingAlgorithm。

  接下來的兩節中comparator方法傳入參數Schedulable類型是一個trait,具體實現主要有兩個:1,Pool;2,TaskSetManager。與最前面那個調度模式的邏輯圖相對應。

1、FIFO模式的調度算法FIFOSchedulingAlgorithm

  在這個類裏面,主要邏輯是一個comparator方法。

  override def comparator(s1: Schedulable, s2: Schedulable): Boolean = {
    val priority1 = s1.priority   //實際上是Job ID
    val priority2 = s2.priority
    var res = math.signum(priority1 - priority2)
    if (res == 0) { //如果Job ID相同,就比較Stage ID
      val stageId1 = s1.stageId
      val stageId2 = s2.stageId
      res = math.signum(stageId1 - stageId2)
    }
    if (res < 0) {
      true
    } else {
      false
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

如果有兩個調度任務s1和s2,首先獲得兩個任務的priority,在FIFO中該優先級實際上是Job ID。首先比較兩個任務的Job ID,如果priority1比priority2小,那麼返回true,表示s1的優先級比s2的高。我們知道Job ID是順序生成的,先生成的Job ID比較小,所以先提交的job肯定比後提交的job先執行。但是如果是同一個job的不同任務,接下來就比較各自的Stage ID,類似於比較Job ID,Stage ID小的優先級高。

2、FAIR模式的調度算法FairSchedulingAlgorithm

  這個類中的comparator方法源代碼如下:

  override def comparator(s1: Schedulable, s2: Schedulable): Boolean = {
    val minShare1 = s1.minShare //在這裏share理解成份額,即每個調度池要求的最少cpu核數
    val minShare2 = s2.minShare
    val runningTasks1 = s1.runningTasks // 該Pool或者TaskSetManager中正在運行的任務數
    val runningTasks2 = s2.runningTasks
    val s1Needy = runningTasks1 < minShare1 // 如果正在運行任務數比該調度池最小cpu核數要小
    val s2Needy = runningTasks2 < minShare2
    val minShareRatio1 = runningTasks1.toDouble / math.max(minShare1, 1.0).toDouble
    val minShareRatio2 = runningTasks2.toDouble / math.max(minShare2, 1.0).toDouble
    val taskToWeightRatio1 = runningTasks1.toDouble / s1.weight.toDouble
    val taskToWeightRatio2 = runningTasks2.toDouble / s2.weight.toDouble
    var compare: Int = 0

    if (s1Needy && !s2Needy) {
      return true
    } else if (!s1Needy && s2Needy) {
      return false
    } else if (s1Needy && s2Needy) {
      compare = minShareRatio1.compareTo(minShareRatio2)
    } else {
      compare = taskToWeightRatio1.compareTo(taskToWeightRatio2)
    }

    if (compare < 0) {
      true
    } else if (compare > 0) {
      false
    } else {
      s1.name < s2.name
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

  minShare對應fairscheduler.xml配置文件中的minShare屬性。 
(1)如果s1所在Pool或者TaskSetManager中運行狀態的task數量比minShare小,s2所在Pool或者TaskSetManager中運行狀態的task數量比minShare大,那麼s1會優先調度。反之,s2優先調度。 
(2)如果s1和s2所在Pool或者TaskSetManager中運行狀態的task數量都比各自minShare小,那麼minShareRatio小的優先被調度。 
minShareRatio是運行狀態task數與minShare的比值,即相對來說minShare使用較少的先被調度。 
(3)如果minShareRatio相同,那麼最後比較各自Pool的名字。

1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章