在大數據開發中,我們可能會遇到大數據計算中一個最棘手的問題——數據傾斜,此時Spark作業的性能會比期望差很多。數據傾斜調優,就是使用各種技術方案解決不同類型的數據傾斜問題,以保證Spark作業的性能。該篇博客參考美團的spark高級版,修改了代碼使用了scala寫的。
這個方案的核心實現思路就是進行兩階段聚合。第一次是局部聚合,先給每個key都打上一個隨機數,比如10以內的隨機數,此時原先一樣的key就變成不一樣的了,比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),就會變成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。接着對打上隨機數後的數據,執行reduceByKey等聚合操作,進行局部聚合,那麼局部聚合結果,就會變成了(1_hello, 2) (2_hello, 2)。然後將各個key的前綴給去掉,就會變成(hello,2)(hello,2),再次進行全局聚合操作,就可以得到最終結果了,比如(hello, 4)。
方案實現原理:將原本相同的key通過附加隨機前綴的方式,變成多個不同的key,就可以讓原本被一個task處理的數據分散到多個task上去做局部聚合,進而解決單個task處理數據量過多的問題。接着去除掉隨機前綴,再次進行全局聚合,就可以得到最終的結果。具體原理見下圖。
package com.qf.bigdata.test
import scala.util.Random
import org.apache.spark.{SparkConf, SparkContext}
/**
* 測試
* spark中groupby 產生的數據傾斜問題
*
* 解決方案
*/
object TestGroupBy {
def main(args: Array[String]): Unit = {
val conf=new SparkConf().setAppName("Demo").setMaster("local[2]")
val sc=new SparkContext(conf)
//準備數據
val array=new Array[Int](10000)
for (i <- 0 to 9999){
array(i)=new Random().nextInt(10)
}
//生成一個rdd
val rdd=sc.parallelize(array)
//數據量很大就先取樣
//rdd.sample(false,0.1)
//所有key加一操作
val maprdd=rdd.map((_,1))
//沒有加隨機前綴的結果
println("沒有加隨機前綴的結果(沒有經過處理的rdd)++++++++++++++++++++++++")
maprdd.countByKey.foreach(print)
println("兩階段聚合(局部聚合+全局聚合)處理數據傾斜++++++++++++++++++++++++")
//兩階段聚合(局部聚合+全局聚合)處理數據傾斜
//加隨機前綴
val prifixrdd=maprdd.map(x=>{
val prifix=new Random().nextInt(10)
(prifix+"_"+x._1,x._2)
})
//加上隨機前綴的key進行局部聚合
val tmprdd=prifixrdd.reduceByKey(_+_)
//去除隨機前綴
val newrdd=tmprdd.map(x=> (x._1.split("_")(1),x._2))
//最終聚合
newrdd.reduceByKey(_+_).foreach(print)
}
}
這樣,數據傾斜的問題就解決了。