spark之数据倾斜 什么是数据倾斜
由于数据分布不均匀造成时间差异很大产生的一些列异常现象
常见现象 1、个别task作业运行缓慢 2、莫名其妙的OOM(内存溢出)异常
一、数据倾斜原因
1、针对于个别task作业运行缓慢情况,主要是由于spark作业运行时有两种类型,窄依赖和宽依赖,在进行宽依赖时由于是进行了夸网络传输进行shuffle操作,这是若是某个key值对应数据量过大就会造成这种情况
二、解决数据倾斜
思路,找到这个数据量比较大的可以进行分拆
(1)、在hive ETL中做预处理
Hive ETL预处理,数据倾斜的现象在hive中提前被处理,这样加载到spark中的数据有倾斜吗?没有!此时spark给web服务端只提供一个查询服务,所以没有的数据倾斜,效率非常高!只不过此时将数据倾斜解决掉了吗?是把spark端的dataskew转移到hive中。
(2)、过滤掉发生数据倾斜的key
找到哪些发生数据倾斜的key,同时必须要想业务人员确认这些key是否有用,如果没用直接使用filter算子过滤掉就行。
但在工作中,切忌,但凡是删除、过滤、更新等待操作,一定慎重。
(3)、提高程序的并行度
通过以下两种方法进行设置
- spark.default.parallelism 设置spark程序全局并行度
- shuffle操作的第二个参数进行设置(局部)并行度
提高并行度会在一定程度上减轻数据倾斜的压力,但是并不能从彻底上根除数据倾斜**。因为一旦发生数据倾斜,倾斜的key无论如何提高并行度,经过shuffle操作都会直到一个分区中去。
(4)、进行两阶段聚合
两阶段聚合 局部聚合+全局聚合适用于xxxxByKey操作
package Day24.com.aura.test
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.util.Random
object TwoStageDataskewOps {
def main(args: Array[String]): Unit = {
val conf=new SparkConf()
.setAppName("TwoStageDataskewOps")
.setMaster("local[2]")
val sc=new SparkContext(conf)
val list=List(
"hello hello hello hello you you hello",
"hello hello hello you you hei hei hello hello hello",
"hello hello hello hello you you hello",
"hello hello hello you you hei hei hello hello hello",
"hello hello hello hello you you hello",
"hello hello hello you you hei hei hello hello hello"
)
val listRDD=sc.parallelize(list)
val pairRDD:RDD[(String,Int)]=listRDD.flatMap(line=>{
line.split("\\s+").map((_,1))
})
val sorted=pairRDD.sample(true,0.6).countByValue().toList.sortWith((m1,m2)=>m1._2>m2._2)
println("抽样排序")
//获取抽样排序后的结果将集合转为字符串用换行符分割
println(sorted.mkString("\n"))
//获取发生数据倾斜的key
val daraskewKey=sorted.head._1
//添加随机数
val perfixpairsRDD=pairRDD.map{case(word,count)=>{
if(word==daraskewKey){
//取随机数
val random=new Random()
val prefix=random.nextInt(1)
(s"${prefix}_${word}",count)
}else{
(word,count)
}
}}
//进行局部聚合
val partAggrRDD:RDD[(String,Int)]=perfixpairsRDD.reduceByKey(_+_)
//去掉随机数
val unPrefixpairsRDD=partAggrRDD.map{case (word,count)=>{
//去掉随机前缀
if (word.contains("_")){
(word.substring(2),count)
}else{
(word,count)
}
}}
//进行全局聚合
val fullArreRDd=unPrefixpairsRDD.reduceByKey(_+_)
fullArreRDd.foreach(println)
sc.stop()
}
}
(5)、使用map-join代替reducerjoin
此操作主要针对join类的聚合操作,多表关联,前提是进行大小表关联
例
package Day24.com.aura.test
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.util.Random
object TwoStageDataskewOps {
def main(args: Array[String]): Unit = {
val conf=new SparkConf()
.setAppName("TwoStageDataskewOps")
.setMaster("local[2]")
val sc=new SparkContext(conf)
val list=List(
"hello hello hello hello you you hello",
"hello hello hello you you hei hei hello hello hello",
"hello hello hello hello you you hello",
"hello hello hello you you hei hei hello hello hello",
"hello hello hello hello you you hello",
"hello hello hello you you hei hei hello hello hello"
)
val listRDD=sc.parallelize(list)
val pairRDD:RDD[(String,Int)]=listRDD.flatMap(line=>{
line.split("\\s+").map((_,1))
})
val sorted=pairRDD.sample(true,0.6).countByValue().toList.sortWith((m1,m2)=>m1._2>m2._2)
println("抽样排序")
//获取抽样排序后的结果将集合转为字符串用换行符分割
println(sorted.mkString("\n"))
//获取发生数据倾斜的key
val daraskewKey=sorted.head._1
//添加随机数
val perfixpairsRDD=pairRDD.map{case(word,count)=>{
if(word==daraskewKey){
//取随机数
val random=new Random()
val prefix=random.nextInt(1)
(s"${prefix}_${word}",count)
}else{
(word,count)
}
}}
//进行局部聚合
val partAggrRDD:RDD[(String,Int)]=perfixpairsRDD.reduceByKey(_+_)
//去掉随机数
val unPrefixpairsRDD=partAggrRDD.map{case (word,count)=>{
//去掉随机前缀
if (word.contains("_")){
(word.substring(2),count)
}else{
(word,count)
}
}}
//进行全局聚合
val fullArreRDd=unPrefixpairsRDD.reduceByKey(_+_)
fullArreRDd.foreach(println)
sc.stop()
}
}
(6)、使用采样key进行分拆和聚合
当join操作的是两张大表,一张表正常,一张表中有个别key异常,其余正常。怎么办?
使用采样key进行分拆和聚合
package Day24.com.aura.test
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable.ArrayBuffer
import scala.util.Random
object TwoStageDataskewOps02 {
def main(args: Array[String]): Unit = {
val conf=new SparkConf()
.setAppName("TwoStageDataskewOps02")
.setMaster("local[2]")
val sc = new SparkContext(conf)
val left = List(
("hello", 1),
("hello", 2),
("hello", 3),
("you", 1),
("me", 1),
("you", 2),
("hello", 4),
("hello", 5)
)
val right = List(
("hello", 11),
("hello", 12),
("you", 11),
("me", 12)
)
val leftListRDD:RDD[(String,Int)]=sc.parallelize(left)
val rightListRDD:RDD[(String,Int)]=sc.parallelize(right)
//获取样本数据
val sampleRDD=leftListRDD.sample(true,0.8)
//对样本数据进行排序
val sorted=sampleRDD.countByValue().toList.sortWith((m1,m2)=>m1._2>m2._2)
//找到异常数据key值
val dataskewKey=sorted.head._1
//将表拆分成正常数据和异常数据
val dsLeftRDD:RDD[(String,Int)]=leftListRDD.filter{case (word,count)=>word==dataskewKey}
val commonleftRDD:RDD[(String,Int)]=leftListRDD.filter{case (word,count)=>word!=dataskewKey}
val dsRightRDD:RDD[(String, Int)] = rightListRDD.filter{case (word, count) => word == dataskewKey}
val commonRightRDD:RDD[(String, Int)] = rightListRDD.filter{case (word, count) => word != dataskewKey}
//为异常表的异常数据的key添加随机数
val prefixLeftRDD=dsLeftRDD.map{case (word,count)=>{
val random=new Random()
val prefix=random.nextInt(2)
(s"${prefix}_${word}",count)
}}
//将正常表的与异常表的数据进行扩容
val prefixRightRDD=dsRightRDD.flatMap{case (word,count)=>{
val ab=ArrayBuffer[(String,Int)]()
for(i<- 0 until 2){
ab.append((s"${i}_${word}",count))
}
ab
}}
//进行表的正常部分join连接
val commonJoinedRDd=commonleftRDD.join(commonRightRDD)
//进行表的异常部分进行连接
val dsJoinedRDD=prefixLeftRDD.join(prefixRightRDD)
//除去异常表添加的随机数
val dsFinalJoinedRDD=dsJoinedRDD.map{case (word,count)=>{
(word.substring(word.indexOf("_")+1),count)
}}
//合并异常表数据和正常表数据
val finalJoinedRDD=dsFinalJoinedRDD.union(commonJoinedRDd)
finalJoinedRDD.foreach(println)
sc.stop()
}
}
扩展,两张大表,左表全量异常,右表正常。
没有好的解决方案,左表全量加N以内的随机前缀,右表全量进行N倍的扩容。可能会有的问题,扩容之后的存储压力非常大,可能发生OOM异常。
实际上是以空间换时间
如果说上述的单一操作解决不了问题怎么办?那就一起上!