兩階段聚合(局部聚合+全局聚合)解決groupby產生數據傾斜的簡單案例

1概述

有的時候,我們可能會遇到大數據計算中一個最棘手的問題——數據傾斜,此時Spark作業的性能會比期望差很多。數據傾斜調優,就是使用各種技術方案解決不同類型的數據傾斜問題,以保證Spark作業的性能。該篇博客參考美團的spark高級版,只是修改了代碼使用了scala寫的。

2產生原因

方案適用場景:對RDD執行reduceByKey等聚合類shuffle算子或者在Spark SQL中使用group by語句進行分組聚合時,比較適用這種方案。

方案實現思路:這個方案的核心實現思路就是進行兩階段聚合。第一次是局部聚合,先給每個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處理數據量過多的問題。接着去除掉隨機前綴,再次進行全局聚合,就可以得到最終的結果。具體原理見下圖。

方案優點:對於聚合類的shuffle操作導致的數據傾斜,效果是非常不錯的。通常都可以解決掉數據傾斜,或者至少是大幅度緩解數據傾斜,將Spark作業的性能提升數倍以上。

方案缺點:僅僅適用於聚合類的shuffle操作,適用範圍相對較窄。如果是join類的shuffle操作,還得用其他的解決方案。
這裏寫圖片描述

  • 代碼
import org.apache.spark.{SparkConf, SparkContext}

import scala.util.Random

object Demo {
  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)
    }
    //array.foreach(x=>print(x+","))

    //生成一個rdd
    val rdd=sc.parallelize(array)
    //數據量很大就先取樣
    //rdd.sample(false,0.1)

    //所有key加一操作
    val maprdd=rdd.map((_,1))
    //沒有加隨機前綴的結果
      maprdd.countByKey.foreach(print)
    //(0,976)(5,997)(1,966)(6,959)(9,1004)(2,1051)(7,973)(3,1036)(8,1022)(4,1016)

    //val wc=rdd.map(x=>(x,1)).reduceByKey(_+_)
    //wc.foreach(print)
    //(4,1016)(0,976)(6,959)(8,1022)(2,1051)(1,966)(3,1036)(7,973)(9,1004)(5,997)


    //兩階段聚合(局部聚合+全局聚合)處理數據傾斜

    //加隨機前綴
    val prifix=new Random().nextInt(10)
    val prifixrdd=maprdd.map(x=>(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)

    //(4,1016)(7,973)(5,997)(9,1004)(8,1022)(6,959)(0,976)(3,1036)(2,1051)(1,966)
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章