Spark 的兩種 Shuffle

1. HashShuffle

1. 優化前

在這裏插入圖片描述

1. shuffle write階段,主要就是在一個stage結束計算之後,爲了下一個stage可以執行shuffle類的算子(比如reduceByKey),
而將每個task處理的數據按key進行“劃分”。所謂“劃分”,就是對相同的key執行hash算法,從而將相同key都寫入同
一個磁盤文件中,而每一個磁盤文件都只屬於下游stage的一個task。在將數據寫入磁盤之前,會先將數據寫入內存緩衝
中,當內存緩衝填滿之後,纔會溢寫到磁盤文件中去。

2. 下一個stage的task有多少個,當前stage的每個task就要創建多少份磁盤文件。比如下一個stage總共有100個task,
那麼當前stage的每個task都要創建100份磁盤文件。如果當前stage有50個task,總共有10個Executor,每個
Executor執行5個task,那麼每個Executor上總共就要創建500個磁盤文件,所有Executor上會創建5000個磁盤文件。
由此可見,未經優化的shuffle write操作所產生的磁盤文件的數量是極其驚人的。

3. shuffle read階段,通常就是一個stage剛開始時要做的事情。此時該stage的每一個task就需要將上一個stage的計算
結果中的所有相同key,從各個節點上通過網絡都拉取到自己所在的節點上,然後進行key的聚合或連接等操作。由於shuffle 
write的過程中,map task給下游stage的每個reduce task都創建了一個磁盤文件,因此shuffle read的過程中,每
個reduce task只要從上游stage的所有map task所在節點上,拉取屬於自己的那一個磁盤文件即可。
shuffle read的拉取過程是一邊拉取一邊進行聚合的。每個shuffle read task都會有一個自己的buffer緩衝,每次都
只能拉取與buffer緩衝相同大小的數據,然後通過內存中的一個Map進行聚合等操作。聚合完一批數據後,再拉取下一批數
據,並放到buffer緩衝中進行聚合操作。以此類推,直到最後將所有數據到拉取完,並得到最終的結果。  

2. 優化後

在這裏插入圖片描述

1. 爲了優化HashShuffleManager我們可以設置一個參數,spark.shuffle. consolidateFiles,該參數默認值爲
false,將其設置爲true即可開啓優化機制,通常來說,如果我們使用HashShuffleManager,那麼都建議開啓這個選項。

2. 開啓consolidate機制之後,在shuffle write過程中,task就不是爲下游stage的每個task創建一個磁盤文件了,
此時會出現shuffleFileGroup的概念,每個shuffleFileGroup會對應一批磁盤文件,磁盤文件的數量與下游stage的
task數量是相同的。一個Executor上有多少個CPU core,就可以並行執行多少個task。而第一批並行執行的每個task都
會創建一個shuffleFileGroup,並將數據寫入對應的磁盤文件內。

3. 當Executor的CPU core執行完一批task,接着執行下一批task時,下一批task就會複用之前已有的shuffleFileGroup,
包括其中的磁盤文件,也就是說,此時task會將數據寫入已有的磁盤文件中,而不會寫入新的磁盤文件中。因此,
consolidate機制允許不同的task複用同一批磁盤文件,這樣就可以有效將多個task的磁盤文件進行一定程度上的合併,
從而大幅度減少磁盤文件的數量,進而提升shuffle write的性能。

4. 假設第二個stage有100個task,第一個stage有50個task,總共還是有10個Executor(Executor CPU個數爲1),
每個Executor執行5個task。那麼原本使用未經優化的HashShuffleManager時,每個Executor會產生500個磁盤文件,
所有Executor會產生5000個磁盤文件的。但是此時經過優化之後,每個Executor創建的磁盤文件的數量的計算公式爲:
CPU core的數量 * 下一個stage的task數量,也就是說,每個Executor此時只會創建100個磁盤文件,所有Executor
只會創建1000個磁盤文件。

2. SortShuffle

SortShuffleManager的運行機制主要分成兩種,一種是普通運行機制,另一種是bypass運行機制。當shuffle read task的數量小於等於spark.shuffle.sort. bypassMergeThreshold參數的值時(默認爲200),就會啓用bypass機制。

1. 普通運行機制

在這裏插入圖片描述

1. 在該模式下,數據會先寫入一個內存數據結構中,此時根據不同的shuffle算子,可能選用不同的數據結構。如果是
reduceByKey這種聚合類的shuffle算子,那麼會選用Map數據結構,一邊通過Map進行聚合,一邊寫入內存;如果是join
這種普通的shuffle算子,那麼會選用Array數據結構,直接寫入內存。接着,每寫一條數據進入內存數據結構之後,就會判
斷一下,是否達到了某個臨界閾值。如果達到臨界閾值的話,那麼就會嘗試將內存數據結構中的數據溢寫到磁盤,然後清空內存
數據結構。

2. 在溢寫到磁盤文件之前,會先根據key對內存數據結構中已有的數據進行排序。排序過後,會分批將數據寫入磁盤文件。默
認的batch數量是10000條,也就是說,排序好的數據,會以每批1萬條數據的形式分批寫入磁盤文件。寫入磁盤文件是通過
Java的BufferedOutputStream實現的。BufferedOutputStream是Java的緩衝輸出流,首先會將數據緩衝在內存中,
當內存緩衝滿溢之後再一次寫入磁盤文件中,這樣可以減少磁盤IO次數,提升性能。

3. 一個task將所有數據寫入內存數據結構的過程中,會發生多次磁盤溢寫操作,也就會產生多個臨時文件。最後會將之前所
有的臨時磁盤文件都進行合併,這就是merge過程,此時會將之前所有臨時磁盤文件中的數據讀取出來,然後依次寫入最終的
磁盤文件之中。此外,由於一個task就只對應一個磁盤文件,也就意味着該task爲下游stage的task準備的數據都在這一個
文件中,因此還會單獨寫一份索引文件,其中標識了下游各個task的數據在文件中的start offset與end offset。

4. SortShuffleManager由於有一個磁盤文件merge的過程,因此大大減少了文件數量。比如第一個stage有50個task,
總共有10個Executor,每個Executor執行5個task,而第二個stage有100個task。由於每個task最終只有一個磁盤文
件,因此此時每個Executor上只有5個磁盤文件,所有Executor只有50個磁盤文件。

2. bypass運行機制

在這裏插入圖片描述
bypass運行機制的觸發條件如下:

  1. shuffle map task數量小於spark.shuffle.sort.bypassMergeThreshold參數的值。
  2. 不是聚合類的shuffle算子。
1. 此時,每個task會爲每個下游task都創建一個臨時磁盤文件,並將數據按key進行hash然後根據key的hash值,將key寫入
對應的磁盤文件之中。當然,寫入磁盤文件時也是先寫入內存緩衝,緩衝寫滿之後再溢寫到磁盤文件的。最後,同樣會將所有臨
時磁盤文件都合併成一個磁盤文件,並創建一個單獨的索引文件。

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

3. 而該機制與普通SortShuffleManager運行機制的不同在於:第一,磁盤寫機制不同;第二,不會進行排序。也就是說,啓用
該機制的最大好處在於,shuffle write過程中,不需要進行數據的排序操作,也就節省掉了這部分的性能開銷。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章