概述
有的時候,我們可能會遇到大數據計算中一個最棘手的問題——數據傾斜,此時Spark作業的性能會比期望差很多。數據傾斜調優,就是使用各種技術方案解決不同類型的數據傾斜問題,以保證Spark作業的性能。該篇博客參考美團的spark高級版,使用scala代碼進行簡單的模擬。
方案適用場景
在對RDD使用join類操作,或者是在Spark SQL中使用join語句時,而且join操作中的一個RDD或表的數據量比較小(比如幾百M或者一兩G),比較適用此方案。
方案實現思路
不使用join算子進行連接操作,而使用Broadcast變量與map類算子實現join操作,進而完全規避掉shuffle類的操作,徹底避免數據傾斜的發生和出現。將較小RDD中的數據直接通過collect算子拉取到Driver端的內存中來,然後對其創建一個Broadcast變量;接着對另外一個RDD執行map類算子,在算子函數內,從Broadcast變量中獲取較小RDD的全量數據,與當前RDD的每一條數據按照連接key進行比對,如果連接key相同的話,那麼就將兩個RDD的數據用你需要的方式連接起來。
方案實現原理
普通的join是會走shuffle過程的,而一旦shuffle,就相當於會將相同key的數據拉取到一個shuffle read task中再進行join,此時就是reduce join。但是如果一個RDD是比較小的,則可以採用廣播小RDD全量數據+map算子來實現與join同樣的效果,也就是map join,此時就不會發生shuffle操作,也就不會發生數據傾斜。具體原理如下圖所示。
方案優點
對join操作導致的數據傾斜,效果非常好,因爲根本就不會發生shuffle,也就根本不會發生數據傾斜。
方案缺點
適用場景較少,因爲這個方案只適用於一個大表和一個小表的情況。畢竟我們需要將小表進行廣播,此時會比較消耗內存資源,driver和每個Executor內存中都會駐留一份小RDD的全量數據。如果我們廣播出去的RDD數據比較大,比如10G以上,那麼就可能發生內存溢出了。因此並不適合兩個都是大表的情況。
如果對於hive中的map join熟悉的同學看這幅圖應該很好理解,所以建議大家看看hive中的map join原理,進行對比學習。
代碼部分
import org.apache.spark.{SparkConf, SparkContext}
object BroadCast {
def main(args: Array[String]): Unit = {
val conf=new SparkConf().setAppName("BroadCast").setMaster("local[2]")
val sc=new SparkContext(conf)
val smallRDD=sc.parallelize(Array(
("1","zhangsan"),
("2","lisi"),
("3","wangwu")
)).collectAsMap()
val smallBroadCast=sc.broadcast(smallRDD)
val bigRDD=sc.parallelize(Array(
("1","school1","male"),
("2","school2","female"),
("3","school3","male"),
("4","school4","male"),
("5","school5","female")
)).map(x=>(x._1,x))
val broadCastValue=smallBroadCast.value
bigRDD.mapPartitions(partitions=>{
for ((key,value)<-partitions
if (broadCastValue.contains(key)))
yield(key,broadCastValue.getOrElse(key,""),value._2,value._3)
}).collect().foreach(println)
/** 結果
* (1,zhangsan,school1,male)
* (2,lisi,school2,female)
* (3,wangwu,school3,male)
*/
}
}