一:什么情况会出现数据倾斜?

哪些情况会出现数据倾斜:

  • 1、shuffle的时候,如果这个产生shuffle的字段为空,会出现数据倾斜
  • 2、key有很多,分区数设置的过少,导致很多key聚集在一个分区出现数据倾斜
  • 3、当某一个表中某一个key数据特别多,然后使用group by 就会出现数据倾斜
  • 4、大表 join 小表 ,这两个表中某一个表有某一个key或者某几个key数据比较多,会出现数据倾斜
  • 5、大表 join 大表,其中某一个表分布比较均匀,另一个表存在某一个或者某几个key数据特别多,也会出现数据倾斜
  • 6、大表 join 大表,其中某一个表分布比较均匀,另一个表存在很多key数据特别多,也会出现数据倾斜

二:使用sql如何解决数据倾斜?

1、第一种情况:shuffle的时候,如果这个产生shuffle的字段为空,会出现数据倾斜

解决方案:过滤空值

import org.apache.spark.sql.SparkSession

//1、shuffle的时候,如果这个产生shuffle的字段为空,会出现数据倾斜
//解决方案: 过滤空值
object ProcessData {
  def main(args: Array[String]): Unit = {
    //创建入口
    val spark = SparkSession.builder().appName("parcess").master("local[4]").getOrCreate()
    import spark.implicits._
    //创建数据1
    spark.sparkContext.parallelize(Seq[(Int,String,Int,String)](
      (1,"aa",20,""), (2,"bb",20,""), (3,"vv",20,""), (4,"dd",20,""), (5,"ee",20,""),
      (6,"ss",20,""), (7,"uu",20,""), (8,"qq",20,""), (9,"ww",20,""), (10,"rr",20,""),
      (11,"tt",20,""), (12,"xx",20,"class_02"), (13,"kk",20,"class_03"), (14,"oo",20,"class_01"),
      (15,"pp",20,"class_01")
    )).toDF("id","name","age","clazzId")
      //过滤空值,避免数据倾斜
        .filter("clazzId != ''")
      .createOrReplaceTempView("student")

    //创建数据2
    spark.sparkContext.parallelize(Seq[(String,String)](
      ("class_01","java"), ("class_02","python"), ("class_03","hive")
    )).toDF("clazzId","name")
      .createOrReplaceTempView("class")

    //多表联查
    spark.sql(
      """
        |select s.id,s.name,s.age,c.name
        | from student s left join class c
        | on s.clazzId = c.clazzId
      """.stripMargin).show()

    /**
      * 过滤空值之前的结果
      * +---+----+---+------+
        | id|name|age|  name|
        +---+----+---+------+
        | 14|  oo| 20|  java|
        | 15|  pp| 20|  java|
        | 12|  xx| 20|python|
        | 13|  kk| 20|  hive|
        |  1|  aa| 20|  null|
        |  2|  bb| 20|  null|
        |  3|  vv| 20|  null|
        |  4|  dd| 20|  null|
        |  5|  ee| 20|  null|
        |  6|  ss| 20|  null|
        |  7|  uu| 20|  null|
        |  8|  qq| 20|  null|
        |  9|  ww| 20|  null|
        | 10|  rr| 20|  null|
        | 11|  tt| 20|  null|
        +---+----+---+------+

      过滤空值后的结果
      | id|name|age|  name|
      +---+----+---+------+
      | 14|  oo| 20|  java|
      | 15|  pp| 20|  java|
      | 12|  xx| 20|python|
      | 13|  kk| 20|  hive|
      +---+----+---+------+

      */

  }

}

2.第二种情况:key有很多,分区数设置的过少,导致很多key聚集在一个分区出现数据倾斜

解决方案:修改分区数的配置参数

设置shuffle分区数:
#spark sql shuffle并行度设置
spark.sql.shuffle.partitions="200"

#spark core shuffle并行度设置
spark.default.parallelism=200

3.第三种情况:当某一个表中某一个key数据特别多,然后使用group by 就会出现数据倾斜

解决方案: 局部聚合+全局聚合

import org.apache.spark.sql.{DataFrame, SparkSession}

import scala.util.Random

//3.第三种情况:当某一个表中某一个key数据特别多,然后使用group by 就会出现数据倾斜
//解决方案: 局部聚合+全局聚合
object ProcessData {
  def main(args: Array[String]): Unit = {
    //创建入口
    val spark = SparkSession.builder().appName("parcess").master("local[4]").getOrCreate()
    import spark.implicits._
    //创建数据1
    val source: DataFrame = spark.sparkContext.parallelize(Seq[(Int, String, Int, String)](
      (1, "aa", 20, "class_01"), (2, "bb", 20, "class_01"), (3, "vv", 20, "class_01"), (4, "dd", 20, "class_01"), (5, "ee", 20, "class_01"),
      (6, "ss", 20, "class_01"), (7, "uu", 20, "class_01"), (8, "qq", 20, "class_01"), (9, "ww", 20, "class_01"), (10, "rr", 20, "class_01"),
      (11, "tt", 20, "class_01"), (12, "xx", 20, "class_02"), (13, "kk", 20, "class_03"), (14, "oo", 20, "class_01"),
      (15, "pp", 20, "class_01")
    )).toDF("id", "name", "age", "clazzId")

    //2.创建udf函数,给需要聚合的clazzId字段添加后缀 #加随机数
    val addPrefix=(clazzId:String) => s"${clazzId}#${Random.nextInt(10)}"

    //3.注册udf函数
    spark.udf.register("addPrefix",addPrefix)

    //4.使用udf函数进行追加后缀然后创建临时表
    source.selectExpr("id","name","age","addPrefix(clazzId) clazzId").createOrReplaceTempView("temp")

    //5.局部聚合
    val source2: DataFrame = spark.sql(
      """
select clazzId ,count(1) num
from temp
  group by clazzId
      """.stripMargin)

    //6.创建udf函数,把clazzId的后缀去掉,再进行全局聚合
    val delPrefix = (clazzId:String)=>{
      val head: String = clazzId.split("#").head
      head
    }

    //7.注册udf函数
    spark.udf.register("delPrefix",delPrefix)

    //8.使用delPrefix函数创建新的表
    source2.selectExpr("delPrefix(clazzId) clazzId","num").createOrReplaceTempView("temp2")

    //打印第一次聚合结果
    spark.sql(
      """
        |select * from temp2
      """.stripMargin).show()

    //9.进行全局聚合
    spark.sql(
      """
        |select clazzId , sum(num)
        | from temp2
        |  group by clazzId
      """.stripMargin).show()

    /**
      * 局部聚合结果
      * +--------+---+
        | clazzId|num|
        +--------+---+
        |class_01|  1|
        |class_01|  2|
        |class_01|  2|
        |class_03|  1|
        |class_01|  1|
        |class_01|  1|
        |class_01|  3|
        |class_01|  1|
        |class_01|  1|
        |class_01|  1|
        |class_02|  1|
        +--------+---+

      全局聚合结果
      +--------+--------+
      | clazzId|sum(num)|
      +--------+--------+
      |class_01|      13|
      |class_02|      1|
      |class_03|      1|
      +--------+--------+

      */

  }
}

4.第四种情况:大表 join 小表 ,这两个表中某一个表有某一个key或者某几个key数据比较多,会出现数据倾斜

解决方案:将小表广播出去,就会由原来的reduce join 变成 map join ,避免shuffle操作,从而避免数据倾斜

import org.apache.spark.sql.{DataFrame, SparkSession}

import scala.util.Random

//4.第四种情况:大表 join 小表 ,这两个表中某一个表有某一个key或者某几个key数据比较多,会出现数据倾斜
//解决方案:将小表广播出去,就会由原来的reduce join 变成 map join ,避免shuffle操作,从而避免数据倾斜
object ProcessData {
  def main(args: Array[String]): Unit = {
    //创建入口
    val spark = SparkSession.builder()
        .appName("parcess")
        .master("local[4]")
        .config("spark.sql.autoBroadcastJoinThreshold","10485760")
        .getOrCreate()
    import spark.implicits._
    //创建数据1
    spark.sparkContext.parallelize(Seq[(Int, String, Int, String)](
      (1, "aa", 20, "class_01"), (2, "bb", 20, "class_01"), (3, "vv", 20, "class_01"), (4, "dd", 20, "class_01"), (5, "ee", 20, "class_01"),
      (6, "ss", 20, "class_01"), (7, "uu", 20, "class_01"), (8, "qq", 20, "class_01"), (9, "ww", 20, "class_01"), (10, "rr", 20, "class_01"),
      (11, "tt", 20, "class_01"), (12, "xx", 20, "class_02"), (13, "kk", 20, "class_03"), (14, "oo", 20, "class_01"),
      (15, "pp", 20, "class_01")
    )).toDF("id", "name", "age", "clazzId")
        .createOrReplaceTempView("student")

    //创建数据2
    spark.sparkContext.parallelize(Seq[(String,String)](
      ("class_01","java"), ("class_02","python"), ("class_03","hive")
    )).toDF("clazzId","name")
      .createOrReplaceTempView("class")

    //把小表广播出去,前提需要设置自动广播大小的配置
    spark.sql("cache table class")

    //聚合
    spark.sql(
      """
        |select s.id,s.name,s.age,c.name
        | from student s left join class c
        |  on s.clazzId = c.clazzId
      """.stripMargin)
      //把分区和分区的数据打印出来
          .rdd
              .mapPartitionsWithIndex((index,it)=>{
                println(s"index_${index}  data_${it.toBuffer}")
                it
              }).collect()

    /**
      *广播前的数据分布:
      * index_51  data_ArrayBuffer([1,aa,20,java], [2,bb,20,java],[3,vv,20,java], [4,dd,20,java],[5,ee,20,java], [6,ss,20,java],
      *                            [7,uu,20,java],[8,qq,20,java], [9,ww,20,java], [10,rr,20,java], [11,tt,20,java], [14,oo,20,java], [15,pp,20,java])
      *
      * 广播后的结果分布:
      * index_0  data_ArrayBuffer([1,aa,20,java], [2,bb,20,java], [3,vv,20,java])
        index_1  data_ArrayBuffer([4,dd,20,java], [5,ee,20,java], [6,ss,20,java], [7,uu,20,java])
        index_2  data_ArrayBuffer([8,qq,20,java], [9,ww,20,java], [10,rr,20,java], [11,tt,20,java])
        index_3  data_ArrayBuffer([12,xx,20,python], [13,kk,20,hive], [14,oo,20,java], [15,pp,20,java])
      */

  }
}

5.第五种i情况:大表 join 大表,其中某一个表分布比较均匀,另一个表存在某一个或者某几个key数据特别多,也会出现数据倾斜

解决方案步骤:

1、将产生数据倾斜的key过滤出来单独处理[加盐] 和扩容(只有一个或者少量key发生倾斜,可以扩容的倍数10-50倍)
2、没有数据倾斜key的数据照常处理

import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}

import scala.util.Random

//5.第五种i情况:大表 join 大表,其中某一个表分布比较均匀,另一个表存在某一个或者某几个key数据特别多,也会出现数据倾斜
//解决方案步骤:
  //  1、将产生数据倾斜的key过滤出来单独处理[加盐]和扩容
  //  2、没有数据倾斜key的数据照常处理
object ProcessData {
  def main(args: Array[String]): Unit = {
    //创建入口
    val spark = SparkSession.builder()
        .appName("parcess")
        .master("local[4]")
        //.config("spark.sql.autoBroadcastJoinThreshold","10485760")
        .getOrCreate()
    import spark.implicits._
    //创建数据1
    val source1 = spark.sparkContext.parallelize(Seq[(Int, String, Int, String)](
      (1, "aa", 20, "class_01"), (2, "bb", 20, "class_01"), (3, "vv", 20, "class_01"), (4, "dd", 20, "class_01"), (5, "ee", 20, "class_01"),
      (6, "ss", 20, "class_01"), (7, "uu", 20, "class_01"), (8, "qq", 20, "class_01"), (9, "ww", 20, "class_01"), (10, "rr", 20, "class_01"),
      (11, "tt", 20, "class_01"), (12, "xx", 20, "class_02"), (13, "kk", 20, "class_03"), (14, "oo", 20, "class_01"),
      (15, "pp", 20, "class_01")
    )).toDF("id", "name", "age", "clazzId")

    //创建数据2
    val source2 = spark.sparkContext.parallelize(Seq[(String,String)](
      ("class_01","java"), ("class_02","python"), ("class_03","hive")
    )).toDF("clazzId","name")

    //采样,获取数据倾斜的key
    /**
      * 参数1: 是否有放回取样,设置为false才不会干扰抽样的准确性
      * 参数2: 抽样比例,一般大的数据量设置为0.1~0.2 , 这里数据量太小,就设置比较大
      */

    source1.sample(false,0.5).show()

    /**
      * 抽样结果:可以看出class_01数据量非常多,是为倾斜的数据
      * +---+----+---+--------+
        | id|name|age| clazzId|
        +---+----+---+--------+
        |  2|  bb| 20|class_01|
        |  5|  ee| 20|class_01|
        |  7|  uu| 20|class_01|
        |  8|  qq| 20|class_01|
        | 10|  rr| 20|class_01|
        | 11|  tt| 20|class_01|
        +---+----+---+--------+

      */

    //处理步骤:
    //1.从有数据倾斜的表中过滤出倾斜的key
    val source1Yes: Dataset[Row] = source1.filter("clazzId = 'class_01' ")
    //2.从有数据倾斜的表中过滤出没有倾斜的key
    val source1No: Dataset[Row] = source1.filter("clazzId != 'class_01' ")
    //3.从没有数据倾斜的表过滤出数据倾斜的key
    val source2Yes = source2.filter("clazzId = 'class_01'")
    //4.从没有数据倾斜的表过滤出没有数据倾斜的key
    val source2No: Dataset[Row] = source2.filter("clazzId != 'class_01'")

    //没有数据倾斜的数据正常进行join
    source1No.createOrReplaceTempView("studentNo")
    source2No.createOrReplaceTempView("classNo")

    spark.sql(
      """
        |select s.id,s.name,s.age,c.name
        |from studentNo s left join  classNo c
        |on s.clazzId = c.clazzId
      """.stripMargin).createOrReplaceTempView("temp1")

    //自定义UDF函数
    //创建udf函数,给需要聚合的clazzId字段添加后缀 #加随机数
    val addPrefix=(clazzId:String) => s"${clazzId}#${Random.nextInt(10)}"
    //创建udf函数,给clazzId添加固定后缀
    val fixed=(i:Int,clazzId:String)=> s"${clazzId}#${i}"
    //创建udf函数,把clazzId的后缀去掉
    val delPrefix = (clazzId:String)=>{
      val head: String = clazzId.split("#").head
      head
    }
    //注册udf函数
    spark.udf.register("addPrefix",addPrefix)
    spark.udf.register("fixed",fixed)
    spark.udf.register("delPrefix",delPrefix)

    //有数据倾斜的数据进行加盐
    source1Yes.selectExpr("id","name","age","addPrefix(clazzId) clazzId").createOrReplaceTempView("student_temp")

    //定义扩容方法
    def capacity(source:DataFrame): DataFrame ={
      //创建空的dataframe
      val rdd: RDD[Row] = spark.sparkContext.emptyRDD[Row]
      var emptyDataframe: DataFrame = spark.createDataFrame(rdd,source.schema)
      //循环1-10,给clazzId添加固定后缀
      for (i <- 0 until(10)){
        emptyDataframe = emptyDataframe.union(source.selectExpr(s"fixed(${i},clazzId)","name"))
      }
      emptyDataframe
    }

    //对没有数据倾斜的数据中过滤出来的数据进行扩容
    //调用扩容方法
    capacity(source2Yes).createOrReplaceTempView("clazz_temp")
    //有数据倾斜的数据进行join
  spark.sql(
      """
        |select s.id,s.name,s.age,c.name
        |from student_temp s left join clazz_temp c
        |on s.clazzId = c.clazzId
      """.stripMargin)
        .createOrReplaceTempView("temp2")

    spark.sql(
      """
        |select * from temp1
        |union
        |select * from temp2
      """.stripMargin)
      .show()

    /**
      * 结果展示:
      *
      * 1.没有数据倾斜的join
      * +---+----+---+------+
        | id|name|age|  name|
        +---+----+---+------+
        | 12|  xx| 20|python|
        | 13|  kk| 20|  hive|
        +---+----+---+------+

      2.有数据清洗的join
      +---+----+---+----+
      | id|name|age|name|
      +---+----+---+----+
      |  3|  vv| 20|java|
      |  7|  uu| 20|java|
      |  8|  qq| 20|java|
      | 14|  oo| 20|java|
      |  4|  dd| 20|java|
      |  2|  bb| 20|java|
      |  6|  ss| 20|java|
      | 10|  rr| 20|java|
      |  1|  aa| 20|java|
      |  9|  ww| 20|java|
      | 11|  tt| 20|java|
      | 15|  pp| 20|java|
      |  5|  ee| 20|java|
      +---+----+---+----+

      3.union数据结果
      +---+----+---+------+
      | id|name|age|  name|
      +---+----+---+------+
      | 10|  rr| 20|  java|
      |  9|  ww| 20|  java|
      | 15|  pp| 20|  java|
      |  1|  aa| 20|  java|
      |  3|  vv| 20|  java|
      |  7|  uu| 20|  java|
      |  4|  dd| 20|  java|
      | 11|  tt| 20|  java|
      | 12|  xx| 20|python|
      |  6|  ss| 20|  java|
      | 13|  kk| 20|  hive|
      |  5|  ee| 20|  java|
      |  2|  bb| 20|  java|
      | 14|  oo| 20|  java|
      |  8|  qq| 20|  java|
      +---+----+---+------+

      */

  }
}

6.第六种情况:大表 join 大表,其中某一个表分布比较均匀,另一个表存在很多key数据特别多,也会出现数据倾斜

解决方案:直接对产生数据倾斜的表进行加盐[0-9],对另一个表进行扩容,有很多key数据特别多[最多扩容10倍],否则浪费内存

import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}

import scala.util.Random

//6.大表 join 大表,其中某一个表分布比较均匀,另一个表存在很多key数据特别多,也会出现数据倾斜
//* 解决方案:
  //*    直接对产生数据倾斜的表进行加盐[0-9],对另一个表进行扩容[最多扩容10倍]
object ProcessData {
  def main(args: Array[String]): Unit = {
    //创建入口
    val spark = SparkSession.builder()
        .appName("parcess")
        .master("local[4]")
        //.config("spark.sql.autoBroadcastJoinThreshold","10485760")
        .getOrCreate()
    import spark.implicits._
    //创建数据1
    val source1 = spark.sparkContext.parallelize(Seq[(Int, String, Int, String)](
      (1, "aa", 20, "class_01"), (2, "bb", 20, "class_01"), (3, "vv", 20, "class_01"), (4, "dd", 20, "class_01"), (5, "ee", 20, "class_01"),
      (6, "ss", 20, "class_01"), (7, "uu", 20, "class_01"), (8, "qq", 20, "class_01"), (9, "ww", 20, "class_01"), (10, "rr", 20, "class_01"),
      (11, "tt", 20, "class_01"), (12, "xx", 20, "class_02"), (13, "kk", 20, "class_03"), (14, "oo", 20, "class_01"),
      (15, "pp", 20, "class_01")
    )).toDF("id", "name", "age", "clazzId")

    //创建数据2
    val source2 = spark.sparkContext.parallelize(Seq[(String,String)](
      ("class_01","java"), ("class_02","python"), ("class_03","hive")
    )).toDF("clazzId","name")

    //采样,获取数据倾斜的key
    /**
      * 参数1: 是否有放回取样,设置为false才不会干扰抽样的准确性
      * 参数2: 抽样比例,一般大的数据量设置为0.1~0.2 , 这里数据量太小,就设置比较大
      */

    source1.sample(false,0.5).show()
    //假设source1有很多key发生数据倾斜

    //自定义UDF函数
    //创建udf函数,给需要聚合的clazzId字段添加后缀 #加随机数
    val addPrefix=(clazzId:String) => s"${clazzId}#${Random.nextInt(10)}"
    //创建udf函数,给clazzId添加固定后缀
    val fixed=(i:Int,clazzId:String)=> s"${clazzId}#${i}"

    //注册udf函数
    spark.udf.register("addPrefix",addPrefix)
    spark.udf.register("fixed",fixed)

    //有数据倾斜的数据进行加盐
    source1.selectExpr("id","name","age","addPrefix(clazzId) clazzId").createOrReplaceTempView("student_temp")

    //定义扩容方法
    def capacity(source:DataFrame): DataFrame ={
      //创建空的dataframe
      val rdd: RDD[Row] = spark.sparkContext.emptyRDD[Row]
      var emptyDataframe: DataFrame = spark.createDataFrame(rdd,source.schema)
      //循环1-10,给clazzId添加固定后缀
      for (i <- 0 until(10)){
        emptyDataframe = emptyDataframe.union(source.selectExpr(s"fixed(${i},clazzId)","name"))
      }
      emptyDataframe
    }

    //对没有数据倾斜的数据进行扩容
    //调用扩容方法
    capacity(source2).createOrReplaceTempView("clazz_temp")

  //多表联查
  spark.sql(
      """
        |select s.id,s.name,s.age,c.name
        |from student_temp s left join clazz_temp c
        |on s.clazzId = c.clazzId
      """.stripMargin)
          .show()

    /**
      * 结果:
      *+---+----+---+------+
      | id|name|age|  name|
      +---+----+---+------+
      |  7|  uu| 20|  java|
      |  5|  ee| 20|  java|
      |  8|  qq| 20|  java|
      |  2|  bb| 20|  java|
      |  3|  vv| 20|  java|
      | 13|  kk| 20|  hive|
      | 12|  xx| 20|python|
      |  1|  aa| 20|  java|
      |  4|  dd| 20|  java|
      |  6|  ss| 20|  java|
      | 10|  rr| 20|  java|
      | 11|  tt| 20|  java|
      | 14|  oo| 20|  java|
      | 15|  pp| 20|  java|
      |  9|  ww| 20|  java|
      +---+----+---+------+
      */

  }
}