重要 | Spark分割槽並行度決定機制

大資料學習與分享發表於2020-11-19

最近經常有小夥伴在本公眾號留言,核心問題都比較類似,就是雖然接觸Spark有一段時間了,但是搞不明白一個問題,為什麼我從HDFS上載入不同的檔案時,列印的分割槽數不一樣,並且好像spark.default.parallelism這個引數時不是一直起作用?其實筆者之前的文章已有相關介紹,想知道為什麼,就必須瞭解Spark在載入不同的資料來源時分割槽決定機制以及呼叫不用運算元時並行度決定機制以及分割槽劃分。

其實之前的文章《Spark的分割槽》《通過spark.default.parallelism談Spark並行度》已有所介紹,筆者今天再做一次詳細的補充,建議大家在對Spark有一定了解的基礎上,三篇文章結合一起看。

大家都知道Spark job中最小執行單位為task,合理設定Spark job每個stage的task數是決定效能好壞的重要因素之一,但是Spark自己確定最佳並行度的能力有限,這就要求我們在瞭解其中內在機制的前提下,去各種測試、計算等來最終確定最佳引數配比。

Spark任務在執行時會將RDD劃分為不同的stage,一個stage中task的數量跟最後一個RDD的分割槽數量相同。之前已經介紹過,stage劃分的關鍵是寬依賴,而寬依賴往往伴隨著shuffle操作。對於一個stage接收另一個stage的輸入,這種操作通常都會有一個引數numPartitions來顯示指定分割槽數。最典型的就是一些ByKey運算元,比如groupByKey(numPartitions: Int),但是這個分割槽數需要多次測試來確定合適的值。首先確定父RDD中的分割槽數(通過rdd.partitions().size()可以確定RDD的分割槽數),然後在此基礎上增加分割槽數,多次除錯直至在確定的資源任務能夠平穩、安全的執行。

對於沒有父RDD的RDD,比如通過載入HDFS上的資料生成的RDD,它的分割槽數由InputFormat切分機制決定。通常就是一個HDFS block塊對應一個分割槽,對於不可切分檔案則一個檔案對應一個分割槽。

對於通過SparkContext的parallelize方法或者makeRDD生成的RDD分割槽數可以直接在方法中指定,如果未指定,則參考spark.default.parallelism的引數配置。下面是預設情況下確定defaultParallelism的原始碼:

override def defaultParallelism(): Int = {
    conf.getInt("spark.default.parallelism", math.max(totalCoreCount.get(), 2))
}

通常,RDD的分割槽數與其所依賴的RDD的分割槽數相同,除非shuffle。但有幾個特殊的運算元:

1. coalesce和repartition運算元

筆者先放兩張關於該coalesce運算元分別在RDD和DataSet中的原始碼圖:(DataSet是Spark SQL中的分散式資料集,後邊說到Spark時再細講)

通過coalesce原始碼分析,無論是在RDD中還是DataSet,預設情況下coalesce不會產生shuffle,此時通過coalesce建立的RDD分割槽數小於等於父RDD的分割槽數。 

筆者這裡就不放repartition運算元的原始碼了,分析起來也比較簡單,圖中我有所提示。但筆者建議,如下兩種情況,請使用repartition運算元:

1)增加分割槽數repartition觸發shuffle,shuffle的情況下可以增加分割槽數。

coalesce預設不觸發shuffle,即使呼叫該運算元增加分割槽數,實際情況是分割槽數仍然是當前的分割槽數。

2)極端情況減少分割槽數,比如將分割槽數減少為1調整分割槽數為1,此時資料處理上游stage並行度降,很影響效能。此時repartition的優勢即不改變原來stage的並行度就體現出來了,在大資料量下,更為明顯。但需要注意,因為repartition會觸發shuffle,而要衡量好shuffle產生的代價和因為用repartition增加並行度帶來的效益。

 

2. union運算元

還是直接看原始碼:

 

通過分析原始碼,RDD在呼叫union運算元時,最終生成的RDD分割槽數分兩種情況:1)union的RDD分割槽器已定義並且它們的分割槽器相同

多個父RDD具有相同的分割槽器,union後產生的RDD的分割槽器與父RDD相同且分割槽數也相同。比如,n個RDD的分割槽器相同且是defined,分割槽數是m個。那麼這n個RDD最終union生成的一個RDD的分割槽數仍是m,分割槽器也是相同的

2)不滿足第一種情況,則通過union生成的RDD的分割槽數為父RDD的分割槽數之和4.cartesian運算元

通過上述coalesce、repartition、union運算元介紹和原始碼分析,很容易分析cartesian運算元的原始碼。通過cartesian得到RDD分割槽數是其父RDD分割槽數的乘積。

在Spark SQL中,任務並行度引數則要參考spark.sql.shuffle.partitions,筆者這裡先放一張圖,詳細的後面講到Spark SQL時再細說:

看下圖在Spark流式計算中,通常將SparkStreaming和Kafka整合,這裡又分兩種情況:

1. Receiver方式生成的微批RDD即BlockRDD,分割槽數就是block數

2. Direct方式生成的微批RDD即kafkaRDD,分割槽數和kafka分割槽數一一對應
等說到Spark流式處理時再詳細闡述。


關注微信公眾號:大資料學習與分享,獲取更對技術乾貨

相關文章