三、spark--spark調度原理分析

[TOC]

一、wordcount程序的執行過程

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object WordCount {
  def main(args: Array[String]): Unit = {
    //創建spark配置文件對象.設置app名稱,master地址,local表示爲本地模式。
    //如果是提交到集羣中,通常不指定。因爲可能在多個集羣匯上跑,寫死不方便
    val conf = new SparkConf().setAppName("wordCount")

    //創建spark context對象
    val sc = new SparkContext(conf)

    sc.textFile(args(0)).flatMap(_.split(" "))
      .map((_,1))
      .reduceByKey(_+_)
        .saveAsTextFile(args(1))

    sc.stop()
  }
}

核心代碼很簡單,首先看 textFile這個函數

SparkContext.scala

  def textFile(
      path: String,
      minPartitions: Int = defaultMinPartitions): RDD[String] = withScope {
    assertNotStopped()
    //指定文件路徑、輸入的格式類爲textinputformat,輸出的key類型爲longwritable,輸出的value類型爲text
    //map(pair => pair._2.toString)取出前面的value,然後將value轉爲string類型
    //最後將處理後的value返回成一個新的list,也就是RDD[String]
    //setName(path) 設置該file名字爲路徑
    hadoopFile(path, classOf[TextInputFormat], classOf[LongWritable], classOf[Text],
      minPartitions).map(pair => pair._2.toString).setName(path)
  }

關鍵性的操作就是:
返回了一個hadoopFile,它有幾個參數:
path:文件路徑
classOf[TextInputFormat]:這個其實就是輸入文件的處理類,也就是我們mr中分析過的TextInputFormat,其實就是直接拿過來的用的,不要懷疑,就是醬紫的
classOf[LongWritable], classOf[Text]:這兩個其實可以猜到了,就是輸入的key和value的類型。

接着執行了一個map(pair => pair._2.toString),將KV中的value轉爲string類型

我們接着看看hadoopFile 這個方法

 def hadoopFile[K, V](
      path: String,
      inputFormatClass: Class[_ <: InputFormat[K, V]],
      keyClass: Class[K],
      valueClass: Class[V],
      minPartitions: Int = defaultMinPartitions): RDD[(K, V)] = withScope {
    assertNotStopped()

    // This is a hack to enforce loading hdfs-site.xml.
    // See SPARK-11227 for details.
    FileSystem.getLocal(hadoopConfiguration)

    // A Hadoop configuration can be about 10 KB, which is pretty big, so broadcast it.
    val confBroadcast = broadcast(new SerializableConfiguration(hadoopConfiguration))
    val setInputPathsFunc = (jobConf: JobConf) => FileInputFormat.setInputPaths(jobConf, path)

    //看到這裏,最後返回的是一個 HadoopRDD 對象
    //指定sc對象,配置文件、輸入方法類、KV類型、分區個數
    new HadoopRDD(
      this,
      confBroadcast,
      Some(setInputPathsFunc),
      inputFormatClass,
      keyClass,
      valueClass,
      minPartitions).setName(path)
  }

最後返回HadoopRDD對象。

接着就是flatMap(.split(" ")) .map((,1)),比較簡單

flatMap(_.split(" ")) 
就是將輸入每一行,按照空格切割,然後切割後的元素稱爲一個新的數組。
然後將每一行生成的數組合併成一個大數組。

map((_,1))
將每個元素進行1的計數,組成KV對,K是元素,V是1

接着看.reduceByKey(_+_)

這個其實就是將同一key的KV進行聚合分組,然後將同一key的value進行相加,最後就得出某個key對應的value,也就是某個單詞的個數

看看這個函數
def reduceByKey(func: (V, V) => V): RDD[(K, V)] = self.withScope {
    reduceByKey(defaultPartitioner(self), func)
  }
 這個過程中會分區,默認分區數是2,使用的是HashPartitioner進行分區,可以指定分區的最小個數

二、spark的資源調度

2.1 資源調度流程

三、spark--spark調度原理分析

​ 圖2.1 spark資源調度

1、執行提交命令,會在client客戶端啓動一個spark-submit進程(用來爲Driver申請資源)。
2、爲Driver向Master申請資源,在Master的waitingDrivers 集合中添加這個Driver要申請的信息。Master查看works集合,挑選出合適的Work節點。
3、在選中的Work節點中啓動Driver進程(Driver進程已經啓動了,spark-submit的使命已經完成了,關閉該進程)。所以其實driver也需要資源,也只是跑在executor上的一個線程而已
4、Driver進程爲要運行的Application申請資源(這個資源指的是Executor進程)。此時Master的waitingApps 中要添加這個Application申請的資源信息。這時要根據申請資源的要求去計算查看需要用到哪些Worker節點(每一個節點要用多少資源)。在這些節點啓動Executor進程。
(注:輪詢啓動Executor。Executor佔用這個節點1G內存和這個Worker所能管理的所有的core)
5、此時Driver就可以分發任務到各個Worker節點的Executor進程中運行了。

Master中的三個集合

val works = new HashSet[WorkInfo]()
  works 集合採用HashSet數組存儲work的節點信息,可以避免存放重複的work節點。爲什麼要避免重複?首先我們要知道work節點有可能因爲某些原因掛掉,掛掉之後下一次與master通信時會報告給master,這個節點掛掉了,然後master會在works對象裏把這個節點去掉,等下次再用到這個節點是時候,再加進來。這樣來說,理論上是不會有重複的work節點的。可是有一種特殊情況:work掛掉了,在下一次通信前又自己啓動了,這時works裏面就會有重複的work信息。

  val waitingDrivers = new ArrayBuffer[DriverInfo]()
  當客戶端向master爲Driver申請資源時,會將要申請的Driver的相關信息封裝到master節點的DriverInfo這個泛型裏,然後添加到waitingDrivers 裏。master會監控這個waitingDrivers 對象,當waitingDrivers集合中的元素不爲空時,說明有客戶端向master申請資源了。此時應該先查看一下works集合,找到符合要求的worker節點,啓動Driver。當Driver啓動成功後,會把這個申請信息從waitingDrivers 對象中移除。

   val waitingApps = new ArrayBuffer[ApplicationInfo]()
  Driver啓動成功後,會爲application向master申請資源,這個申請信息封存到master節點的waitingApps 對象中。同樣的,當waitingApps 集合不爲空,說明有Driver向Master爲當前的Application申請資源。此時查看workers集合,查找到合適的Worker節點啓動Executor進程,默認的情況下每一個Worker只是爲每一個Application啓動一個Executor,這個Executor會使用1G內存和所有的core。啓動Executor後把申請信息從waitingApps 對象中移除。

  注意點:上面說到master會監控這三個集合,那麼到底是怎麼監控的呢???
  master並不是分出來線程專門的對這三個集合進行監控,相對而言這樣是比較浪費資源的。master實際上是‘監控’這三個集合的改變,當這三個集合中的某一個集合發生變化時(新增或者刪除),那麼就會調用schedule()方法。schedule方法中封裝了上面提到的處理邏輯。

2.2 application和executor的關係

1、默認情況下,每一個Worker只會爲每一個Application啓動一個Executor。每個Executor默認使用1G內存和這個Worker所能管理的所有的core。
2、如果想要在一個Worker上啓動多個Executor,在提交Application的時候要指定Executor使用的core數量(避免使用該worker所有的core)。提交命令:spark-submit --executor-cores
3、默認情況下,Executor的啓動方式是輪詢啓動,一定程度上有利於數據的本地化。

什麼是輪詢啓動???爲什麼要輪訓啓動呢???

  輪詢啓動:輪詢啓動就是一個個的啓動。例如這裏有5個人,每個人要發一個蘋果+一個香蕉。輪詢啓動的分發思路就是:五個人先一人分一個蘋果,分發完蘋果再分發香蕉。

  爲什麼要使用輪詢啓動的方式呢???我們做大數據計算首先肯定想的是計算找數據。在數據存放的地方直接計算,而不是把數據搬過來再計算。我們有n臺Worker節點,如果只是在數據存放的節點計算。只用了幾臺Worker去計算,大部分的worker都是閒置的。這種方案肯定不可行。所以我們就使用輪詢方式啓動Executor,先在每一臺節點都允許一個任務。

  存放數據的節點由於不需要網絡傳輸數據,所以肯定速度快,執行的task數量就會比較多。這樣不會浪費集羣資源,也可以在存放數據的節點進行計算,在一定程度上也有利於數據的本地化。

2.3 spark的粗細粒度調度

粗粒度(富二代):

在任務執行之前,會先將資源申請完畢,當所有的task執行完畢,纔會釋放這部分資源。
優點:每一個task執行前。不需要自己去申請資源了,節省啓動時間。
缺點:等到所有的task執行完纔會釋放資源(也就是整個job執行完成),集羣的資源就無法充分利用。

這是spark使用的調度粒度,主要是爲了讓stage,job,task的執行效率高一點

細粒度(窮二代):

Application提交的時候,每一個task自己去申請資源,task申請到資源纔會執行,執行完這個task會立刻釋放資源。
優點:每一個task執行完畢之後會立刻釋放資源,有利於充分利用資源。
缺點:由於需要每一個task自己去申請資源,導致task啓動時間過長,進而導致stage、job、application啓動時間延長。

2.4 spark-submit提交任務對資源的限制

我們提交任務時,可以指定一些資源限制的參數:

--executor-cores : 單個executor使用的core數量,不指定的話默認使用該worker所有能調用的core
--executor-memory : 單個executor使用的內存大小,如1G。默認是1G
--total-executor-cores : 整個application最多使用的core數量,防止獨佔整個集羣資源

三、整個spark資源調度+任務調度的流程

3.1 總的調度流程

https://blog.csdn.net/qq_33247435/article/details/83653584#3Spark_51

一個application的調度到完成,需要經過以下階段:
application-->資源調度-->任務調度(task)-->並行計算-->完成
三、spark--spark調度原理分析

​ 圖3.1 spark調度流程

可以看到,driver啓動後,會有下面兩個對象:

DAGScheduler:
據RDD的寬窄依賴關係將DAG有向無環圖切割成一個個的stage,將stage封裝給另一個對象taskSet,taskSet=stage,然後將一個個的taskSet給taskScheduler。

taskScheduler:
taskSeheduler拿倒taskSet之後,會遍歷這個taskSet,拿到每一個task,然後去調用HDFS上的方法,獲取數據的位置,根據獲得的數據位置分發task到響應的Worker節點的Executor進程中的線程池中執行。並且會根據每個task的執行情況監控,等到所有task執行完成後,就告訴master將所喲executor殺死

任務調度中主要涉涉及到以下流程:

 1)、DAGScheduler:根據RDD的寬窄依賴關係將DAG有向無環圖切割成一個個的stage,將stage封裝給另一個對象taskSet,taskSet=stage,然後將一個個的taskSet給taskScheduler。

2)、taskScheduler:taskSeheduler拿倒taskSet之後,會遍歷這個taskSet,拿到每一個task,然後去調用HDFS上的方法,獲取數據的位置,根據獲得的數據位置分發task到響應的Worker節點的Executor進程中的線程池中執行。

3)、taskScheduler:taskScheduler節點會跟蹤每一個task的執行情況,若執行失敗,TaskScher會嘗試重新提交,默認會重試提交三次,如果重試三次依然失敗,那麼這個task所在的stage失敗,此時TaskScheduler向DAGScheduler做彙報。

4)DAGScheduler:接收到stage失敗的請求後,,此時DAGSheduler會重新提交這個失敗的stage,已經成功的stage不會重複提交,只會重試這個失敗的stage。
(注:如果DAGScheduler重試了四次依然失敗,那麼這個job就失敗了,job不會重試

掉隊任務的概念:

當所有的task中,75%以上的task都運行成功了,就會每隔一百秒計算一次,計算出目前所有未成功任務執行時間的中位數*1.5,凡是比這個時間長的task都是掙扎的task。

總的調度流程:

=======================================資源調度=========================================
1、啓動Master和備用Master(如果是高可用集羣需要啓動備用Master,否則沒有備用Master)。
2、啓動Worker節點。Worker節點啓動成功後會向Master註冊。在works集合中添加自身信息。
3、在客戶端提交Application,啓動spark-submit進程。僞代碼:spark-submit --master --deploy-mode cluster --class jarPath
4、Client向Master爲Driver申請資源。申請信息到達Master後在Master的waitingDrivers集合中添加該Driver的申請信息。
5、當waitingDrivers集合不爲空,調用schedule()方法,Master查找works集合,在符合條件的Work節點啓動Driver。啓動Driver成功後,waitingDrivers集合中的該條申請信息移除。Client客戶端的spark-submit進程關閉。
(Driver啓動成功後,會創建DAGScheduler對象和TaskSchedule對象)
6、當TaskScheduler創建成功後,會向Master會Application申請資源。申請請求發送到Master端後會在waitingApps集合中添加該申請信息。
7、當waitingApps集合中的元素髮生改變,會調用schedule()方法。查找works集合,在符合要求的worker節點啓動Executor進程。
8、當Executor進程啓動成功後會將waitingApps集合中的該申請信息移除。並且向TaskSchedule反向註冊。此時TaskSchedule就有一批Executor的列表信息。

=======================================任務調度=========================================
9、根據RDD的寬窄依賴,切割job,劃分stage。每一個stage是由一組task組成的。每一個task是一個pipleline計算模式。
10、TaskScheduler會根據數據位置分發task。(taskScheduler是如何拿到數據位置的???TaskSchedule調用HDFS的api,拿到數據的block塊以及block塊的位置信息)
11、TaskSchedule分發task並且監控task的執行情況。
12、若task執行失敗或者掙扎。會重試這個task。默認會重試三次。
13、若重試三次依舊失敗。會把這個task返回給DAGScheduler,DAGScheduler會重試這個失敗的stage(只重試失敗的這個stage)。默認重試四次。
14、告訴master,將集羣中的executor殺死,釋放資源。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章