一:什么情况会出现数据倾斜?
哪些情况会出现数据倾斜:
- 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|
+---+----+---+------+
*/
}
}