Spark 爲啥比 MapReduce 快?

Spark 爲啥比 MapReduce 快?

DAG優化 和 內存

(1) 算子靈活性:MR只支持Map和Reduce 兩種操作,而Spark有豐富的算子。

(2) Map 中間結果寫磁盤,Reduce 寫HDFS,多個MR之間通過HDFS交換數據。

(3) DAG引擎,先劃分爲Stage,Stage之間才Shuffle落盤,Stage之內,都可以內存處理。

(4) spark 中的rdd數據可以緩存到內存中,充分使用內存,多次使用,減少IO。

進程和線程

(1) MR的任務調度和啓動都是進程級別的,每個進程都是JVM,資源和時間開銷都很大。

(2) spark開啓的JVM是Driver和Executor,每個Executor內部可以在每個core上都生成一個task,spark的task是基於線程的,線程池模型有效減少task的啓動開銷,一個executor上可以佔用多個core,每最終task並行度爲executor * core的數量。

Shuffle

(2) MR的Map端和Reduce端均需要排序。Spark在Shuffle過程中,儘量避免不必要的Sort操作。

一、Spark算子 VS MapReduce算子

MR只有Map 和 Reduce 兩種操作。而spark基於RDD構建了豐富的算子。

RDD:Resilient Distribute DataSets,分佈式彈性數據集

(1)RDD是分佈於集羣中的,有多個Partition組成的只讀對象集合。

分區數的確定

(2)支持內存、磁盤等多種存儲級別。

(3)通過並行的 Transform 操作,逐步構建需要的結果。

(4)通過Lineage血統體系,支持自動重構。

Transform操作:生成新的RDD

PartitionBy ,map, filter,groupBy,reduceBy ,reduceByKey

Action 操作:獲取一個或者一組值

count,reduce,saveAsTextFile

## 二、Spark Shuffle VS MR Shuffle

MapReduce Shuffle過程

MapReduce Shuffle過程

1、首先 map 在做輸出時候會在內存裏開啓一個環形內存緩衝區,專門用來做輸出,同時map還會啓動一個守護線程;

2、如緩衝區的內存達到了閾值的80%,守護線程就會把內容寫到磁盤上,這個過程叫spill,另外的20%內存可以繼續寫入要寫進磁盤的數據;

3、寫入磁盤和寫入內存操作是互不干擾的,如果緩存區被撐滿了,那麼map就會阻塞寫入內存的操作,讓寫入磁盤操作完成後再繼續執行寫入內存操作;

4、寫入磁盤時會有個內存排序操作,如果定義了combiner函數,那麼排序前還會執行combiner操作;

5、每次spill操作也就是寫入磁盤操作時候就會寫一個溢出文件,也就是說在做map輸出有幾次spill就會產生多少個溢出文件等map輸出全部做完後,map會合並這些輸出文件,這個過程裏還會有一個Partitioner操作(打標籤,看當前數據應該分到哪個Reducer),確定數據應該分配到哪個Reducer。

6、最後 reduce 就是合併map輸出文件,Partitioner會找到對應的map輸出文件,然後進行復制操作,複製操作時reduce會開啓幾個複製線程,這些線程默認個數是5個(可修改),這個複製過程和map寫入磁盤過程類似,也有閾值和內存大小,閾值一樣可以在配置文件裏配置,而內存大小是直接使用reduce的tasktracker的內存大小,複製時候reduce還會進行排序操作和合並複製線程負責過來的小文件操作,這些操作完了就會進行reduce計算了。 reduce階段:由我們自己編寫,最終結果存儲在hdfs上的。

Spark Shuffle過程

shuffle過程只有在stage與stage之間纔會運行,前一個stage可以看作是MR的MapTask;後面的stage可以看作是ReduceTask,而且stage的切分規則是根據RDD的寬窄依賴關係切分的.

完全依賴是寬依賴。

部分依賴就是窄依賴。

寬依賴和窄依賴04.png

Spark 中涉及 Shuffle 的算子

去重

def distinct()
def distinct(numPartitions: Int)

分組聚合

def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]
def reduceByKey(partitioner: Partitioner, func: (V, V) => V): RDD[(K, V)]
def groupBy[K](f: T => K, p: Partitioner):RDD[(K, Iterable[V])]
def groupByKey(partitioner: Partitioner):RDD[(K, Iterable[V])]
def combineByKey[C](createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C): RDD[(K, C)]

排序

def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length): RDD[(K, V)]
def sortBy[K](f: (T) => K, ascending: Boolean = true, numPartitions: Int = this.partitions.length)(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]

重分區

def coalesce(numPartitions: Int, shuffle: Boolean = false, partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null)

Join操作

def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))]
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
def join[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (V, W))]
def leftOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (V, Option[W]))]

Shuffle Write

爲下游Stage準備上一個Satge輸出的結果文件。

對於Hash Shuffle 來說下一個stage的task即Reduce Task有多少個,當前stage的每個task,或者每個Core就要創建多少份磁盤文件。

對應Sort Shuffle 來說,有多少Map Task 就創建多少磁盤文件 ,和索引文件。

Shuffle Read

接着我們來說說shuffle read。shuffle read,通常就是一個stage剛開始時要做的事情。此時該stage的每一個task就需要將上一個stage的計算結果中的所有相同key,從各個節點上通過網絡都拉取到自己所在的節點上,然後進行key的聚合或連接等操作。由於shuffle write的過程中,task給下游stage的每個task都創建了一個磁盤文件,因此shuffle read的過程中,每個task只要從上游stage的所有task所在節點上,拉取屬於自己的那一個磁盤文件即可。

shuffle read的拉取過程是一邊拉取一邊進行聚合的。每個shuffle read task都會有一個自己的buffer緩衝,每次都只能拉取與buffer緩衝相同大小的數據,然後通過內存中的一個Map進行聚合等操作。聚合完一批數據後,再拉取下一批數據,並放到buffer緩衝中進行聚合操作。以此類推,直到最後將所有數據到拉取完,並得到最終的結果。

HashShuffleMagnage 和SortShuffleManage

Spark程序中的Shuffle操作是通過shuffleManage對象進行管理。Spark目前支持的ShuffleMange模式主要有兩種:HashShuffleMagnage 和SortShuffleManage 不同的地方在Shuffle Write的方式,Shuffle Read方式是一致的。
Shuffle操作包含當前階段的Shuffle Write(存盤)和下一階段的Shuffle Read(fetch),兩種模式的主要差異是在Shuffle Write階段,下面將着重介紹。

HashShuffleWrite

HashShuffle是根據Reduce Task 的個數進行Hash的 , 每個Hash結果文件都只屬於下游stage的一個task,如果涉及到按Key分組(比如reduceByKey),那麼就是key值的hashcode%ReduceTask來決定放入哪一個區分,這樣保證相同的數據一定放入一個分區。

Hash Shuffle過程如下:

HashShuffleWrite02.png

根據爲Reduce階段的Shuffle Read的文件數目進行了優化

爲什麼進行優化?

  1. Write階段創建大量的寫文件的對象
  2. read階段就要進行多次網絡通信,來拉取磁盤小文件
  3. read階段創建大量的讀文件對象

造成的影響,創建的對象過多,會導致JVM內存不足,JVM內存不足又會導致GC垃圾回收OOM。所以之後提出了合併機制的HashShuffle.

優化方式是什麼?

由 M*R個小文件 ==》 變爲 Core * R 個小文件。

HashShuffleWriteBetter05.png

SortShuffleWrite

這種shuffle最終產出的文件個數是 M*2 個,即一個Map Task產出一個磁盤數據文件和一個索引文件(標識Reduce拉取的開始文件句柄)。按照是否排序分爲

ByPass機制 和 普通機制。

優先選用ByPass機制

觸發機制:

  1. shuffle map task數量小於spark.shuffle.sort.bypassMergeThreshold參數(200)的值。
  2. 不是聚合類的shuffle算子(比如reduceByKey),不涉及Key。

SortShuffleWrite07.png

此時task會爲每個下游task都創建一個臨時磁盤文件,並將數據按key進行hash然後根據key的hash值,將key寫入對應的磁盤文件之中。當然,寫入磁盤文件時也是先寫入內存緩衝,緩衝寫滿之後再溢寫到磁盤文件的。最後,同樣會將所有臨時磁盤文件都合併成一個磁盤文件,並創建一個單獨的索引文件。

該過程的磁盤寫機制其實跟未經優化的HashShuffleManager是一模一樣的,因爲都要創建數量驚人的磁盤文件,只是在最後會做一個磁盤文件的合併而已。因此少量的最終磁盤文件,也讓該機制相對未經優化的HashShuffleManager來說,shuffle read的性能會更好。

而該機制與普通SortShuffleManager運行機制的不同在於:第一,磁盤寫機制不同;第二,不會進行排序。也就是說,啓用該機制的最大好處在於,shuffle write過程中,不需要進行數據的排序操作,也就節省掉了這部分的性能開銷。

普通機制

下圖說明了普通的SortShuffleManager的原理。在該模式下,數據會先寫入一個內存數據結構中,此時根據不同的shuffle算子,可能選用不同的數據結構。如果是reduceByKey這種聚合類的shuffle算子,那麼會選用Map數據結構,一邊通過Map進行聚合,一邊寫入內存;如果是join這種普通的shuffle算子,那麼會選用Array數據結構,直接寫入內存。

CommonWrite02.png

其實這裏和MR的運行過程十分相似,當數據寫入內存之前也會進行“打標籤”,當內存滿了之後申請資源申請不下來時,會先進行排序,然後溢寫。

在溢寫到磁盤文件之前,會先根據key對內存數據結構中已有的數據進行排序。排序過後,會分批將數據寫入磁盤文件。但是,這一個Task的整個過程只產生2個磁盤文件,一個是溢寫的磁盤文件,一個是索引文件(其中標識了下游各個task的數據在文件中的start offset與end offset),因爲當第一個磁盤寫文件溢寫完成後,剩下的每次產生的小文件都會與之前的文件進行合併,所以一直只會產生兩個磁盤文件。這 , 就是merge過程

這個內存(Executor中內存)的數據結構(Map 或者 Array)的默認大小約爲5M。那它爲什麼是約爲5M呢?
因爲Executor是一個JVM進程,而Spark只是一個框架,不能準確的控制JVM的資源情況,所以說這個初始值約等於5M,當這5M滿了之後,此時監控此內存結構的監控對象會去申請資源(當前已用資源*2-5M),如果申請到了,則不會溢寫,如果申請不到則會溢寫

總結與MR中shuffle的不同之處

1、 spark中HashShuffle的shuffle write中沒有分組和排序

首先,MR的計算思想就是,相同key值的數據爲一組,每一組調用一次reduce()函數,但是spark中沒有這個說法,所以HashShuffle沒有分組和排序.

2、Spark中SortShuffle的普通運行機制和MR

Spark中SortShuffle的普通運行機制和MR的過程幾乎是一模一樣的,都有寫入內存前的打標籤、內存寫滿之後的排序、溢寫到磁盤;但是有一點不同,就是SortShuffle的內存是約等於5M且動態變化的,而MR的內存是固定100M的(當然可以改配置文件來修改)

3、Spark中SortShuffle的bypass運行機制和MR

Spark中SortShuffle的bypass運行機制中沒有排序,Spark shuffle默認是SortShuffle的bypass運行機制,因爲它沒有排序和分組,所以這也是比MR快的原因之一。

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