Spark中shuffle性能調優1

通過上面的架構和源碼實現的分析,不難得出Shuffle是Spark Core比較復雜的模塊的結論。它也是非常影響性能的操作之一。因此,在這里整理了會影響Shuffle性能的各項配置。盡管大部分的配置項在前文已經解釋過它的含義,由于這些參數的確是非常重要,這里算是做一個詳細的總結。

1.1.1??spark.shuffle.manager

前文也多次提到過,Spark1.2.0官方支持兩種方式的Shuffle,即Hash Based Shuffle和Sort Based Shuffle。其中在Spark 1.0之前僅支持Hash Based Shuffle。Spark 1.1的時候引入了Sort Based Shuffle。Spark 1.2的默認Shuffle機制從Hash變成了Sort。如果需要Hash Based Shuffle,可以將spark.shuffle.manager設置成“hash”即可。

如果對性能有比較苛刻的要求,那么就要理解這兩種不同的Shuffle機制的原理,結合具體的應用場景進行選擇。

Hash Based Shuffle,就是將數據根據Hash的結果,將各個Reducer partition的數據寫到單獨的文件中去,寫數據時不會有排序的操作。這個問題就是如果Reducer的partition比較多的時候,會產生大量的磁盤文件。這會帶來兩個問題:

1)? 同時打開的文件比較多,那么大量的文件句柄和寫操作分配的臨時內存會非常大,對于內存的使用和GC帶來很多的壓力。尤其是在Sparkon YARN的模式下,Executor分配的內存普遍比較小的時候,這個問題會更嚴重。
2)? 從整體來看,這些文件帶來大量的隨機讀,讀性能可能會遇到瓶頸。

更加細節的討論可以參見7.1節和7.6.6(嘗試去解決寫的文件太多的問題)。

#

Sort Based Shuffle會根據實際情況對數據采用不同的方式進行Sort。這個排序可能僅僅是按照Reducer的partition進行排序,保證同一個Shuffle Map Task的對應于不同的Reducer的partition的數據都可以寫到同一個數據文件,通過一個Offset來標記不同的Reducer partition的分界。因此一個Shuffle Map Task僅僅會生成一個數據文件(還有一個index索引文件),從而避免了Hash Based Shuffle文件數量過多的問題。

選擇Hash還是Sort,取決于內存,排序和文件操作等因素的綜合影響。

對于不需要進行排序的Shuffle而且Shuffle產生的文件數量不是特別多,Hash Based Shuffle可能是個更好的選擇;畢竟Sort Based Shuffle至少會按照Reducer的partition進行排序。

而Sort BasedShuffle的優勢就在于Scalability,它的出現實際上很大程度上是解決Hash Based Shuffle的Scalability的問題。由于Sort Based Shuffle還在不斷的演進中,因此Sort Based Shuffle的性能會得到不斷的改善。

對選擇那種Shuffle,如果對于性能要求苛刻,最好還是通過實際的場景中測試后再決定。不過選擇默認的Sort,可以滿足大部分的場景需要。

1.1.2??spark.shuffle.spill

這個參數的默認值是true,用于指定Shuffle過程中如果內存中的數據超過閾值(參考spark.shuffle.memoryFraction的設置),那么是否需要將部分數據臨時寫入外部存儲。如果設置為false,那么這個過程就會一直使用內存,會有Out Of Memory的風險。因此只有在確定內存足夠使用時,才可以將這個選項設置為false。

對于Hash BasedShuffle的Shuffle Write過程中使用的org.apache.spark.util.collection.AppendOnlyMap就是全內存的方式,而org.apache.spark.util.collection.ExternalAppendOnlyMap對org.apache.spark.util.collection.AppendOnlyMap有了進一步的封裝,在內存使用超過閾值時會將它spill到外部存儲,在最后的時候會對這些臨時文件進行Merge。

而Sort BasedShuffle Write使用到的org.apache.spark.util.collection.ExternalSorter也會有類似的spill。

而對于ShuffleRead,如果需要做aggregate,也可能在aggregate的過程中將數據spill的外部存儲。

1.1.3??spark.shuffle.memoryFraction和spark.shuffle.safetyFraction

在啟用spark.shuffle.spill的情況下,spark.shuffle.memoryFraction決定了當Shuffle過程中使用的內存達到總內存多少比例的時候開始Spill。在Spark 1.2.0里,這個值是0.2。通過這個參數可以設置Shuffle過程占用內存的大小,它直接影響了Spill的頻率和GC。

如果Spill的頻率太高,那么可以適當的增加spark.shuffle.memoryFraction來增加Shuffle過程的可用內存數,進而減少Spill的頻率。當然為了避免OOM(內存溢出),可能就需要減少RDD cache所用的內存,即需要減少spark.storage.memoryFraction的值;但是減少RDD cache所用的內存有可能會帶來其他的影響,因此需要綜合考量。

在Shuffle過程中,Shuffle占用的內存數是估計出來的,并不是每次新增的數據項都會計算一次占用的內存大小,這樣做是為了降低時間開銷。但是估計也會有誤差,因此存在實際使用的內存數比估算值要大的情況,因此參數?spark.shuffle.safetyFraction作為一個保險系數降低實際Shuffle過程所需要的內存值,降低實際內存超出用戶配置值的風險。

1.1.4??spark.shuffle.sort.bypassMergeThreshold

這個配置的默認值是200,用于設置在Reducer的partition數目少于多少的時候,Sort Based Shuffle內部不使用Merge Sort的方式處理數據,而是直接將每個partition寫入單獨的文件。這個方式和Hash Based的方式是類似的,區別就是在最后這些文件還是會合并成一個單獨的文件,并通過一個index索引文件來標記不同partition的位置信息。從Reducer看來,數據文件和索引文件的格式和內部是否做過Merge Sort是完全相同的。

這個可以看做SortBased Shuffle在Shuffle量比較小的時候對于Hash Based Shuffle的一種折衷。當然了它和Hash Based Shuffle一樣,也存在同時打開文件過多導致內存占用增加的問題。因此如果GC比較嚴重或者內存比較緊張,可以適當的降低這個值。

1.1.5??spark.shuffle.blockTransferService

在Spark 1.2.0,這個配置的默認值是netty,而之前是nio。這個主要是用于在各個Executor之間傳輸Shuffle數據。Netty的實現更加簡潔,但實際上用戶不用太關心這個選項。除非是有特殊的需求,否則采用默認配置就可以。

1.1.6??spark.shuffle.consolidateFiles

這個配置的默認配置是false。主要是為了解決在Hash Based Shuffle過程中產生過多文件的問題。如果配置選項為true,那么對于同一個Core上運行的Shuffle Map Task不會新產生一個Shuffle文件而是重用原來的。但是每個Shuffle Map Task還是需要產生下游Task數量的文件,因此它并沒有減少同時打開文件的數量。如果需要了解更加詳細的細節,可以閱讀7.1節。

但是consolidateFiles的機制在Spark 0.8.1就引入了,到Spark 1.2.0還是沒有穩定下來。從源碼實現的角度看,實現源碼是非常簡單的,但是由于涉及本地的文件系統等限制,這個策略可能會帶來各種各樣的問題。由于它并沒有減少同時打開文件的數量,因此不能減少由文件句柄帶來的內存消耗。如果面臨Shuffle的文件數量非常大,那么是否打開這個選項最好還是通過實際測試后再決定。

1.1.7??spark.shuffle.service.enabled

(false)

1.1.8??spark.shuffle.compress和 spark.shuffle.spill.compress

?這兩個參數的默認配置都是true。spark.shuffle.compress和spark.shuffle.spill.compress都是用來設置Shuffle過程中是否對Shuffle數據進行壓縮;其中前者針對最終寫入本地文件系統的輸出文件,后者針對在處理過程需要spill到外部存儲的中間數據,后者針對最終的shuffle輸出文件。

#如何設置spark.shuffle.compress?

如果下游的Task通過網絡獲取上游Shuffle Map Task的結果的網絡IO成為瓶頸,那么就需要考慮將它設置為true:通過壓縮數據來減少網絡IO。由于上游Shuffle Map Task和下游的Task現階段是不會并行處理的,即上游Shuffle Map Task處理完成,然后下游的Task才會開始執行。因此如果需要壓縮的時間消耗就是Shuffle MapTask壓縮數據的時間 + 網絡傳輸的時間 + 下游Task解壓的時間;而不需要壓縮的時間消耗僅僅是網絡傳輸的時間。因此需要評估壓縮解壓時間帶來的時間消耗和因為數據壓縮帶來的時間節省。如果網絡成為瓶頸,比如集群普遍使用的是千兆網絡,那么可能將這個選項設置為true是合理的;如果計算是CPU密集型的,那么可能將這個選項設置為false才更好。

#如何設置spark.shuffle.spill.compress?

如果設置為true,代表處理的中間結果在spill到本地硬盤時都會進行壓縮,在將中間結果取回進行merge的時候,要進行解壓。因此要綜合考慮CPU由于引入壓縮解壓的消耗時間和Disk IO因為壓縮帶來的節省時間的比較。在Disk IO成為瓶頸的場景下,這個被設置為true可能比較合適;如果本地硬盤是SSD,那么這個設置為false可能比較合適。

1.1.9??spark.reducer.maxMbInFlight

這個參數用于限制一個ReducerTask向其他的Executor請求Shuffle數據時所占用的最大內存數,尤其是如果網卡是千兆和千兆以下的網卡時。默認值是48MB。設置這個值需要中和考慮網卡帶寬和內存。

#

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1、 性能調優 1.1、 分配更多資源 1.1.1、分配哪些資源? Executor的數量 每個Executor所...
    Frank_8942閱讀 4,618評論 2 36
  • 1.1、 分配更多資源 1.1.1、分配哪些資源? Executor的數量 每個Executor所能分配的CPU數...
    miss幸運閱讀 3,215評論 3 15
  • spark-submit的時候如何引入外部jar包 在通過spark-submit提交任務時,可以通過添加配置參數...
    博弈史密斯閱讀 2,792評論 1 14
  • 1.分配更多的資源 -- 性能調優的王道 真實項目里的腳本: bin/spark-submit \ --c...
    evan_355e閱讀 1,893評論 0 0
  • 昨晚看了幾篇文章,有觸動有收獲。 第一篇是關于寫作的,說到寫作要先閱讀,不能只看網文,要讀書,要系統的吸收知識。 ...
    林安君閱讀 313評論 0 1