Spark的join什麼情況下可以避免shuffle?

Spark的join操作可能觸發shuffle操作。shuffle操作要經過磁盤IO,網絡傳輸,對性能影響比較大。本文聊一聊Spark的join在哪些情況下可以避免shuffle過程。

1 DataFrame/Dataset的join如何避免shuffle

針對Spark DataFrame/DataSet的join,可以通過broadcast join和bucket join來避免shuffle操作。

1.1 Broadcast Join

Broadcast join很好理解,小表被分發到所有executors,所以不需要做shuffle就可以完成join. Spark SQL控制自動broadcast join的參數是:spark.sql.autoBroadcastJoinThreshold , 默認爲10MB. 就是說當join中的一張表的size小於10MB時,spark會自動將其封裝爲broadcast發送到所有結點,然後進行broadcast join. 當然也可以手動將join中的某張錶轉化成broadcast : 

    sparkSession.sparkContext.broadcast(df)

1.2 Bucket Join

Bucket join其實就是將要join的兩張表按照join columns(或join columns的子集)根據相同的partitioner預先做好分區,並將這些分區信息存儲到catalog中(比如HiveExternalCatalog);然後在讀取這兩張表並做join時,spark根據bucket信息將兩張表的相同partition進行join即可,從而避免了shuffle的過程。注意,這裏是避免了shuffle過程,並沒有完全避免網絡傳輸,由於兩張表的相同partition不一定在同一臺機器上,所以這裏仍需要對其中一張表的partition進行網絡傳輸。

2 RDD的join什麼情況下可以避免shuffle

筆者這裏想討論的是PairRDDFunctions類的join方法。在RDD對象中有一個隱式轉換可以將rdd轉換成PairRDDFunctions對象,這樣就可以直接在rdd對象上調用join方法:

 

2.1 PairRDDFunctions.join和PairRDDFunctions.cogroup

先來看看PairRDDFunctions的join方法:

PairRDDFunctions有多個重載的join方法,上面這個只帶一個RDD對象的參數,我們接着看它調用的另一個重載的join方法:

可以看到,RDD的join實現是由cogroup方法完成的,cogroup完後得到的是類型爲RDD[(K, (Iterable[V], Iterable[W]))]的rdd對象,其中K爲key的類型,V爲第一張join表的value類型,W爲第二張join表的value類型;然後,調用flatMapValues將其轉換成RDD[(K, V, W)]的rdd對象。

下面來看看PairRDDFunctions.cogroup方法的實現:

cogroup中生成了CoGroupedRDD對象,所以關鍵是這個RDD的getDependencies方法返回的dependencies中是否存在shuffle dependency.

2.2 CoGroupedRDD

看看這個RDD的getDependencies方法:

其中的rdds就是進行cogroup的rdd序列,也就是PairRDDFunctions.cogroup方法中傳入的Seq(self, other) .

重點來了,對於所有參與cogroup的rdd,如果它的partitioner和結果CoGroupedRDD的partitioner相同,則該rdd會成爲CoGroupedRDD的一個oneToOne窄依賴,否則就是一個shuffle依賴,即寬依賴。

我們知道,只有寬依賴纔會觸發shuffle,所以RDD的join可以避免shuffle的條件是:參與join的所有rdd的partitioner都和結果rdd的partitioner相同。

那麼,結果rdd的partitioner是怎麼確定的呢?上文講到PairRDDFunctions.join方法有多個重載,其中就有可以指定partitioner的重載,如果沒有指定,則使用默認的partitioner,看看默認的partitioner是怎麼確定的:

簡單地說就是:

1. 如果父rdds中有可用的合格的partitioner,則直接使用其中分區數最大的那個partitioner;

2. 如果沒有,則根據默認分區數生成HashPartitioner.

至於怎樣的partitioner是合格的,請讀者閱讀上面的Partitioner.defaultPartitioner方法和Partitioner.isEligiblePartitioner方法。

RDD的compute方法是真正計算得到數據的方法,我們來看看CoGroupedRDD的compute方法是怎麼實現的:

可以看到,CoGroupedRDD的數據是根據不同的依賴從父rdd中獲取的:

1. 對於窄依賴,直接調用父rdd的iterator方法獲取對應partition的數據

2. 對於寬依賴,從shuffleManager獲取shuffleReader對象進行讀取,這裏就是shuffle read了

還有一個重點是讀取多個父rdds的數據後,怎麼將這些數據根據key進行cogroup?

這裏用到了ExternalAppendOnlyMap來構建key和grouped values的映射。先來看看createExternalMap的實現:

相關類型定義如下:

可以看到,ExternalAppendOnlyMap的構造函數的參數是是三個方法參數:

1. createCombiner : 對每個key創建用於合併values的combiner數據結構,在這裏就是一個CoGroup的數據,數組大小就是dependencies的數量

2. mergeValue : 將每個value合併到對應key的combiner數據結構中,在這裏就是將一個CoGroupValue對象添加到其所在rdd對應的CoGroup中

3. mergeCombiners : 合併相同key的兩個combiner數據結構,在這裏就是合併兩個CoGroupCombiner對象

CoGroupedRDD.compute會調用ExternalAppendOnlyMap.insertAll方法將從父rdds得到的數據一個一個地插入到ExternalAppendOnlyMap對象中進行合併。

最後,以這個ExternalAppendOnlyMap對象作爲參數構造InterruptibleIterator,這個iterator會被調用者用於訪問CoGroupedRDD的單個partition的所有數據。

3 總結

本文簡單地介紹了DataFrame/DataSet如何避免join中的shuffle過程,並根據源碼詳述了RDD的join操作的具體實現細節,分析了RDD的join在什麼情況下可以避免shuffle過程。

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