深入淺出Spark Join

大數志發表於2020-11-28


在資料分析和處理的過程中,我們經常會用Join操作來關聯兩個資料集,Spark作為一個通用的分析引擎,能夠支援多種Join的應用場景。

Join操作的輸入是兩個資料集,A和B,將資料集A中的每一條記錄和資料集B中的每一條記錄進行比對,每發現一條符合條件的記錄時,返回一條新的記錄,新記錄中的欄位可以只從A中來,也可以只從B中來,也可以分別從A和B中取一部分,因此,Join後的記錄可以表示兩個資料集中記錄的結合。

影響Spark Join操作的三個因素

具體到Spark中Join操作的執行,有三個影響較大的因素:輸入資料集的大小、Join條件、Join型別。

輸入資料集的大小

輸入資料集的大小直接影響Join操作的效率和可靠性,不只絕對大小,資料集之間的相對大小也對效率和可靠性有影響。

Join條件

Join條件通常是兩個資料集中欄位的邏輯比較,一般可以分為等值Join不等值Join

等值Join可以包含一個相等條件或多個需要同時滿足的相等條件,比如:

  • 一個相等條件:A.x == B.x
  • 多個相等條件:A.x == B.x and A.y == B.y

注:x 和 y 是資料集A和B中的欄位。

不等值Join使用不相等條件或者不能同時滿足的相等條件,比如:

  • 不相等條件:A.x < B.x
  • 不能同時滿足的相等條件:A.x == B.x or A.y == B.y

Join型別

Join型別影響Join操作的輸出,大致包括以下幾類:

  • Inner Join:Inner Join只輸出匹配的記錄(滿足Join條件),記錄來自兩個資料集
  • Outer Join:Outer Join除了輸出匹配的記錄,也輸出未匹配的記錄,根據如何輸出未匹配的記錄,outer Join可以進一步分為left out join、right out join和full outer join,記錄來自兩個資料集
  • Semi Join:Semi Join輸出的記錄只來自一個資料集,要麼是匹配的記錄,要麼是未匹配的記錄。如果輸出的是未匹配的記錄,也叫做Anti Join
  • Cross Join:Cross Join輸出兩個資料集中所有記錄可能的組合,例如,A集合中有m條記錄,B集合中有n條記錄,則結果為m*n條記錄,Cross Join又稱為笛卡爾積。

根據上面的三個因素,Spark會選擇合適的執行機制來完成Join操作。

Spark Join的執行機制

Spark提供了五種執行Join操作的機制,分別是:

  • Shuffle Hash Join
  • Broadcast Hash Join
  • Sort Merge Join
  • Cartesian Join
  • Broadcast Nested Join

Hash Join

Broadcast Hash Join和Shuffle Hash Join都基於Hash Join,Hash Join是單機上的Join操作。想象一道LeetCode演算法題,資料量分別為m和n的兩個陣列,怎麼找到兩個陣列的公共元素?第一種方法:對兩個陣列進行巢狀迴圈的遍歷,發現相等元素則輸出。第二種方法:用空間換時間,將其中一個陣列轉化成集合(Python的set或者Java的HashSet,實現都基於雜湊表),然後遍歷第二個陣列中的每一個元素,判斷是否包含在第一個集合中。Hash Join和第二種方法類似,將較小的資料集分割槽構造成雜湊表,用Join的key作為雜湊表的key,key所對應的記錄作為雜湊表的value,然後遍歷較大的資料集分割槽,在雜湊表中尋找對應的key,找到兩個分割槽key相同的記錄將其輸出。因為使用了雜湊表,所以叫做Hash Join。

根據進行Join的兩個資料集的大小關係,Spark支援兩種Hash Join。

Broadcast Hash Join

當其中一個資料集足夠小時,採用Broadcast Hash Join,較小的資料集會被廣播到所有Spark的executor上,並轉化為一個Hash Table,之後較大資料集的各個分割槽會在各個executor上與Hash Table進行本地的Join,各分割槽Join的結果合併為最終結果。

Broadcast Hash Join 沒有Shuffle階段、效率最高。但為了保證可靠性,executor必須有足夠的記憶體能放得下被廣播的資料集,所以當進兩個資料集的大小都超過一個可配置的閾值之後,Spark不會採用這種Join。控制這個閾值的引數為spark.sql.autoBroadcastJoinThreshold,最新版本(3.0.1)中預設值為10M。

Shuffle Hash Join

當兩個資料集都小於可以使用Broadcast Hash Join的閾值時,採用Shuffle Join,先對兩個資料集進行Shuffle,Shuffle是意思是根據key的雜湊值,對兩個資料集進行重新分割槽,使得兩個資料集中key的雜湊值相同的記錄會被分配到同一個executor上,此時在每個executor上的分割槽都足夠小,各個executor分別執行Hash Join即可。

Shuffle操作會帶來大量的網路IO開銷,因此效率會受到影響。同時,在executor的記憶體使用方面,如果executor的數量足夠多,每個分割槽處理的資料量可以控制到比較小。

Sort Merge Join

Sort Merge Join和Shuffle Hash Join類似,會有一個Shuffle階段,將key相同的記錄重分配同一個executor上,不同的是,在每個executor上,不再構造雜湊表,而是對兩個分割槽進行排序,然後用兩個下標同時遍歷兩個分割槽,如果兩個下標指向的記錄key相同,則輸出這兩條記錄,否則移動key較小的下標。

Sort Merge Join也有Shuffle階段,因此效率同樣不如Broadcast Hash Join。在記憶體使用方面,因為不需要構造雜湊表,需要的記憶體比Hash Join要少。

Cartesian Join

Cartesian Join機制專門用來實現cross join,結果的分割槽數等於輸入資料集的分割槽數之積,結果中每一個分割槽的資料對應一個輸入資料集的一個分割槽和另外一個輸入資料集的一個分割槽。

Cartesian Join會產生非常多的分割槽,但如果要進行cross join,別無選擇。

Broadcast Nested Loop Join

Broadcast Nested Join將一個輸入資料集廣播到每個executor上,然後在各個executor上,另一個資料集的分割槽會和第一個資料集使用巢狀迴圈的方式進行Join輸出結果。

Broadcast Nested Join需要廣播資料集和巢狀迴圈,計算效率極低,對記憶體的需求也極大,因為不論資料集大小,都會有一個資料集被廣播到所有executor上。

Spark如何選擇Join機制

Spark根據以下的因素選擇實際執行Join的機制:

  • 引數配置
  • hint引數
  • 輸入資料集大小
  • Join型別
  • Join條件

其中,hint引數是一種在join時手動指定join機制的方法,例如:

df1.hint("broadcast").join(df2, ...)

下面介紹在什麼情況下使用何種Join機制。

何時使用Broadcast Hash Join

必需條件:

  • 只用於等值Join
  • 不能用於Full Outer Join

以下條件需要滿足一個:

  • 左邊的資料集使用了broadcast hint,Join型別是Right Outer,Right Semi或Inner
  • 沒使用hint,但左邊的資料集小於spark.sql.autoBroadcastJoinThreshold引數,Join型別是Right Outer,Right Semi或Inner
  • 右邊的資料集使用了broadcast hint,Join型別是Left Outer,Left Semi或Inner
  • 沒使用hint,但右邊的資料集小於spark.sql.autoBroadcastJoinThreshold引數,Join型別是Left Outer,Left Semi或Inner
  • 兩個資料集都使用了broadcast hint,Join型別是Left Outer,Left Semi,Right Outer,Right Semi或Inner
  • 沒使用hint,但兩個資料集都小於spark.sql.autoBroadcastJoinThreshold引數,Join型別是Left Outer,Left Semi,Right Outer,Right Semi或Inner

何時使用Shuffle Hash Join

必需條件:

  • 只用於等值Join
  • 不能用於Full Outer Join
  • spark.sql.join.prefersortmergeJoin 引數預設值為true,設定為false

以下條件需要滿足一個:

  • 左邊的資料集使用了shuffle_hash hint,Join型別是Right Outer,Right Semi或Inner
  • 沒使用hint,但左邊的資料集比右邊的資料集顯著小,Join型別是Right Outer,Right Semi或Inner
  • 右邊的資料集使用了shuffle_hash hint,Join型別是Left Outer,Left Semi或Inner
  • 沒使用hint,但右邊的資料集比左邊的資料集顯著小,Join型別是Left Outer,Left Semi或Inner
  • 兩邊的資料集都使用了shuffle_hash hint,Join型別是Left Outer,Left Semi,Right Outer,Right Semi或Inner
  • 沒使用hint,兩個資料集都比較小,Join型別是Left Outer,Left Semi,Right Outer,Right Semi或Inner

何時使用Sort Merge Join

必需條件:

  • 只用於等值Join
  • Join條件中的key是可排序的
  • spark.sql.join.prefersortmergeJoin 引數預設值為true,設定為true

以下條件需要滿足一個:

  • 有一個資料集使用了merge hint,Join型別任意
  • 沒有使用merge hint,Join型別任意

何時使用Cartesian Join

必需條件:

  • Cross Join

以下條件需要滿足一個:

  • 使用了shuffle_replicate_nl hint,是等值或不等值Join均可
  • 沒有使用hint,等值或不等值Join均可

何時Broadcast Nested Loop Join

Broadcast Nested Loop Join是預設的Join機制,當沒有選用其他Join機制被選擇時,用它來進行任意條件任意型別的Join。

當有多種Join機制可用時,選擇的優先順序為Broadcast Hash Join > Sort Merge Join > Shuffle Hash Join > Cartesian Join。

在進行Inner Join和不等值Join時,如果有一個資料集可以被廣播,Broadcast Nested Loop Join的優先順序比Cartesian Join優先順序高。

參考連結:

  1. https://towardsdatascience.com/demystifying-joins-in-apache-spark-38589701a88e
  2. https://blog.csdn.net/lsr40/article/details/99569049
  3. https://www.ruanyifeng.com/blog/2019/01/table-join.html
  4. http://hbasefly.com/2017/03/19/sparksql-basic-join/

公眾號:大數志

傳遞最新、最有價值的大資料技術乾貨和資訊。