Spark 数据倾斜解决方案

目录

什么时候出现了数据倾斜?

spark web ui可以看到task中的数据和时间

数据倾斜原因

场景及解决方法

一:使用Hive ETL预处理数据

适用场景

方案实现

优缺点

二:过滤少量导致倾斜的key

适用场景

方案实现

优缺点

三:提高shuffle并行度

适用场景

方案实现

优缺点

流程图

四:两阶段聚合(聚不聚和+全局聚合)

方案适用场景

优缺点

流程图

代码

五:将reduce join变为map join

适用场景

方案实现原理

缺点

六:采样倾斜key并分拆join操作

适用场景

优缺点

流程

 代码

七:使用随机前缀和扩容RDD进行join

适用场景

实现原理

方案优缺点

八:多种方案组合使用


什么时候出现了数据倾斜?

  • 绝大多数task执行得都非常快,但个别task执行极慢。比如,总共有1000个task,997个task都在1分钟之内执行完了,但是剩余两三个task却要一两个小时。这种情况很常见。
  • 原本能够正常执行的Spark作业,某天突然报出OOM(内存溢出)异常,观察异常栈,是我们写的业务代码造成的。这种情况比较少见。

spark web ui可以看到task中的数据和时间

数据倾斜原因

数据倾斜的原理很简单:在进行shuffle的时候,必须将各个节点上相同的key拉取到某个节点上的一个task来进行处理,比如按照key进行聚合或join等操作。此时如果某个key对应的数据量特别大的话,就会发生数据倾斜

场景及解决方法

一:使用Hive ETL预处理数据

适用场景

导致数据倾斜的是Hive表,如果该Hive表中的数据本身很不均匀(比如某个Key对应了100万条数据,其他key才对应了10条数据),而且业务场景需要频繁使用Spark对Hive表执行某个分析操作。

方案实现

如果spark处理hive表,其中hive表中数据不均,并且要求spark处理要足够快时,可将hive做ETL预处理,将shuffle操作提前到了HIVE ETL

优缺点

优点:实现起来简单便捷,完全规避掉了数据倾斜,spark作业的性能会大幅度提升

缺点:治标不治本,Hive ETL中还是会发生数据倾斜

二:过滤少量导致倾斜的key

适用场景

当发现导致倾斜的key就少数几个,并且对作业的执行和计算结果不是很重要的话,就干脆直接过滤掉那几个key

方案实现

比如:在spark sql中使用where自居过滤掉这些key,或者spark rdd中执行filter算子过滤掉这些key

如果需要每次作业执行时,动态判定哪些key的数量最多然后进行过滤,那么可以使用sample算子对RDD进行采样,然后计算出每个key的数量,取数据量最多的key过滤掉即可

优缺点

优点:实现简单,效果好

缺点:适用场景不多,大都数情况下,导致倾斜的key还是很多的,并不是只有少数几个

三:提高shuffle并行度

适用场景

如果必须要对数据倾斜迎难而上,那么建议有限使用这种方案,因为这是处理数据倾斜最简单的一种方案

方案实现

reduceByKey(1000),groupByKey(48) 增大shuffle时task数量

使用spark sql的话,使用

spark.conf.set("spark.sql.shuffle.partitions",500) //shuffle read task ,默认是200

优缺点

优点:提高shuffle操作得并行度,根据hash算的哪个key放哪个分区,当分区多时,就能减轻数据倾斜。

缺点:但是对于某个key多分到一个分区,无法解决。

流程图

四:两阶段聚合(聚不聚和+全局聚合)

方案适用场景

对RDD执行reduceByKey等聚合类shuffle算子或者在spark sql中使用group by语句进行分组聚合时,比较适用

优缺点

优点:对于聚合类的shuffle操作导致的数据倾斜,效果非常不错,spark作业性能提升数倍以上

缺点:只适用聚合类shuffle操作,如果时join类的shuffle操作,还得用其他的解决方案

流程图

代码

package com.cy

import org.apache.spark.{SparkConf, SparkContext}

import scala.util.Random

object DataSkew {

  def main(args: Array[String]): Unit = {

    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("testRdd");

    sparkConf.set("spark.default.parallelism", "2")

    val sc = new SparkContext(sparkConf);
    val rdd=sc.makeRDD(List(("hello",1),("hello1",1),("hello",1),("hello",1)))

   val stageOne= rdd.map(x=>
    {
     val prifix=new Random().nextInt(2)
      (prifix+"_"+x._1,x._2)
    })

    val newRdd=stageOne.reduceByKey(_+_)
    val stageTwo=newRdd.map(x=>(x._1.split("_")(1),x._2))
    stageTwo.reduceByKey(_+_).foreach(println)
    
  }
}

五:将reduce join变为map join

适用场景

当一个表中的数据只有几百M或者1、2G

方案实现原理

普通的join会产生shuffle过程,就相当于会将相同key的数据拉取到一个shuffle read task中,再进行join,此时就是reduce join

但是如果一个RDD是比较小的,则可以采用广播小RDD全量数据+map算子来实现join同样的效果,也就是map join,此时就不会发生shuffle操作,也就不会发生数据倾斜

缺点

缺点: 值适用于其中一个表数据比较小的情况,不适用两个都是大表的情况

六:采样倾斜key并分拆join操作

适用场景

两个RDD/Hive表进行join的时候,如果数据量都比较大,无法采用”解决方案五“,那么此时可以看一下两个RDD/Hive表中的key分布情况。

如果出现数据倾斜,是因为其中某一个RDD/Hive表中的少数几个key的数据量过大,而另一个RDD/Hive表中的所有key都分布比较均匀,那么采用这个解决方案是比较合适的。

优缺点

优点:对于join导致的数据倾斜,如果只是某几个key导致了倾斜,采用该方式可以用最有效的方式打散key进行join。而且只需要针对少数倾斜key对应的数据进行扩容n倍,不需要对全量数据进行扩容。避免了占用过多内存。

缺点:如果导致倾斜的key特别多的话,比如成千上万个key都导致数据倾斜,那么这种方式也不合适

流程

 代码

数据1:text.txt

001 hello
001 hello
001 hello
002 hello
002 hello
003 hello
003 hello
003 hello
003 hello
003 hello
003 hello

数据2:text1.txt

001 wawa01
002 wawa02
003 wawa03

inner join代码

package com.cy

import org.apache.spark.sql.{DataFrame, Dataset, SparkSession}
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
import scala.util.Random

case class AdClickLog(c1:String,c2:String)

case class Skew(c1:String,c2:String,c3:String)
object SparkDataSkewDemo {

  def main(args: Array[String]): Unit = {

    val userId = null
    val spark = SparkSession
      .builder()
      .master("local[*]")
      .appName("SparkSessionZipsExample")
      .getOrCreate()
    val sc=spark.sparkContext

    import spark.implicits._

   val buffer= new ArrayBuffer[String]()

   for (i<-1 to 3){
     buffer.+=(i+"")
   }

    val df = spark.read.textFile("D:\\spark-project\\src\\main\\resources\\text.txt")


    val df1 = spark.read.textFile("D:\\spark-project\\src\\main\\resources\\text1.txt")


    val ds = df.map(x => {

      val row = x.split(" ")
      AdClickLog(row(0), row(1))

    })

    val ds1 = df1.map(x => {

      val row = x.split(" ")
      AdClickLog(row(0), row(1))

    })

    val id = ds.sample(false, 0.5)
      .map(x => (x.c1, 1)).groupByKey(_._1).count().sort($"count(1)".desc).rdd.take(1)

    val bc=sc.broadcast(id)
    val bcBuffer=sc.broadcast(buffer)

    val commonRDD = ds.filter(x => !x.c1.contains("003"))


    val skewRDD=ds.filter(x => x.c1.contains("003")).map(x=>{
      val random=new Random().nextInt(3)
      (random+1+"_"+x.c1,x.c2)
    })
    //skewRDD.show()

    val skewRDD1=ds1.filter(x => x.c1.contains("003")).flatMap(x=>{
      bcBuffer.value.map(y=>(y+"_"+x.c1,x.c2))
    })

   // skewRDD1.show()


    val skewJoinRdd:DataFrame=skewRDD.join(skewRDD1,Seq("_1"),"inner")

    val skewJoinRdd1=skewJoinRdd.map(row=>{

      Skew(row(0).toString.split("_")(1),row(1).toString,row(2).toString)
    })

   // skewJoinRdd1.show()

    val joinRDD2=commonRDD.join(ds1,Seq("c1"), "inner")
    //
    commonRDD.join(ds1, Seq("c1"), "inner").show()
   skewJoinRdd1.toDF().union(joinRDD2).show()
  }

}

七:使用随机前缀和扩容RDD进行join

适用场景

如果在进行join操作时,RDD中有大量的key导致数据倾斜,那么进行分拆key也没什么意义,此时就只能适用最后一种方案来解决问题了

实现原理

与方案“六”类似,首先查看RDD/Hive表中的数据分布情况,找到那个造成数据倾斜的RDD/Hive表,比如有多个key都对应了超过1万条数据。

然后将该RDD的每条数据都打上一个n以内的随即前缀

同时对另外一个正常的RDD进行扩容,将每条数据都扩容成n条数据,扩容出来的每条数据都一次打上一个0~n的前缀。

方案优缺点

优点:对join类型的数据倾斜基本都可以处理,而且效果也相对比较显著,性能提升效果非常不错。

缺点:该方案更多的是缓解数据倾斜,而不是彻底避免数据倾斜。而且需要对整个RDD进行扩容,堆内存资源要求很高

八:多种方案组合使用

多数情况下,复杂的数据倾斜场景,可能需要将多种方案组合使用

根据业务场景,使用1-7方案来组合使用

创作不易,感谢支持,您的支持是我创作最大的动力

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值