package com.spark.optimization.p2
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* 过滤掉少数几个发生数据倾斜的key,这样这些key便不会参与计算,
* 也就不会再发生数据倾斜dataskew了。
* 需要注意的一点:
* 在过滤之前一定要和业务人员/产品/项目经理进行确认是否需要当前key。
* 如果不需要:过滤掉,执行当前优化方案,
* 如果需要:只需要使用后续的优化方案。
*
* 过滤的动作必须要在发生数据倾斜之前完成。
*/
object _01FilterDataSkewOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName(s"${_01FilterDataSkewOps.getClass.getSimpleName}")
val sc = new SparkContext(conf)
//加载数据
val list = List(
"hello you", "hello me", "hello hello hello hello hello hello", "hello you", "hello me"
)
val listRDD:RDD[String] = sc.parallelize(list)
//计算没有单词出现的次数
val wordsRDD:RDD[String] = listRDD.flatMap(line => line.split("\\s+"))
//没有过滤之前的操作
val pairRDD:RDD[(String, Int)] = wordsRDD.map((_, 1))
// val rtb = pairRDD.reduceByKey(_+_)
/*
执行过滤操作
因为要过滤的key不一定清楚,需要我们每一次都要手工来进行判断
所以使用sample算子来进行局部的统计,来预估整体的情况
*/
val sampleRDD = pairRDD.sample(false, 0.4)
val sampleRBKRDD:RDD[(String, Int)] = sampleRDD.reduceByKey(_+_)
val top1:Array[(String, Int)] = sampleRBKRDD.takeOrdered(1)(new Ordering[(String, Int)]() {
override def compare(x: (String, Int), y: (String, Int)) = {
y._2.compareTo(x._2)
}
})
println("--------------------->可能发生数据倾斜的可以为:")
top1.foreach(println)
// rtb.foreach(println)
/*
从结果集中提取发生数据倾斜的key
一般情况下,集合都是大对象,所以如果在transformation用到了集合,请使用广播变量将该集合进行包装
避免在每一个task中都复制当前集合的副本。
*/
val dataSkewKeys:Array[String] = top1.map{case (word, count) => word}
//过滤掉发生数据倾斜的key
val filteredRDD:RDD[(String, Int)] = pairRDD.filter{case (word, count) => {
!dataSkewKeys.contains(word)
}}
println("--------------------->过滤掉发生dataskewkey之后的集合内容:")
filteredRDD.foreach(println)
println("--------------------->过滤掉发生dataskewkey之后的结果统计:")
filteredRDD.reduceByKey(_+_).foreach(println)
sc.stop()
}
}
2.
package com.spark.optimization.p2
import org.apache.log4j.{Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.util.Random
/**
* 使用两阶段聚合操作来处理由于聚合类操作,比如reduceByKey,groupByKey,aggregateByKey等待产生的数据倾斜。
* 两阶段聚合:
* 局部聚合
* 将发生数据倾斜的key进行拆分,添加随机前缀来进行拆分,在此基础之上进行聚合统计
* hello 1, hello 1, hello 1, hello 1 ==拆分=>
* 1_hello 1, 0_hello 1, 0_hello 1, 1_hello 1 ==局部聚合==>
* 1_hello 2, 0_hello 2
* 全局聚合
* 在局部聚合的基础之上,去掉随机前缀进行整体的聚合操作
* 1_hello 2, 0_hello 2 ==去掉随机前缀==>
* hello 2, hello 2 ==全局聚合==>
* hello 4
*/
object _02TwoStageAggrDataSkewOps {
def main(args: Array[String]): Unit = {
Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.spark-project").setLevel(Level.WARN)
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName(s"${_02TwoStageAggrDataSkewOps.getClass.getSimpleName}")
val sc = new SparkContext(conf)
//加载数据
val list = List(
"hello you", "hello me", "hello hello hello hello hello hello", "hello you", "hello me"
)
val listRDD:RDD[String] = sc.parallelize(list)
//计算没有单词出现的次数
val wordsRDD:RDD[String] = listRDD.flatMap(line => line.split("\\s+"))
//没有过滤之前的操作
val pairRDD:RDD[(String, Int)] = wordsRDD.map((_, 1))
pairRDD.cache()
// val rtb = pairRDD.reduceByKey(_+_)//发生数据倾斜了
val sampleRDD = pairRDD.sample(false, 0.4)
val sampleRBKRDD:RDD[(String, Int)] = sampleRDD.reduceByKey(_+_)
val top1:Array[(String, Int)] = sampleRBKRDD.takeOrdered(1)(new Ordering[(String, Int)]() {
override def compare(x: (String, Int), y: (String, Int)) = {
y._2.compareTo(x._2)
}
})
println("--------------------->可能发生数据倾斜的可以为:")
top1.foreach(println)
val dataSkewKeys:Array[String] = top1.map{case (word, count) => word}
/开始局部聚合
println("/开始局部聚合/")
val random = new Random()
val prefixPairRDD:RDD[(String, Int)] = pairRDD.map{case (word, count) => {
if(dataSkewKeys.contains(word)) {
//添加随机前缀
val prefix = random.nextInt(2)// 0 1
(s"${prefix}_${word}", count)
} else {
(word, count)
}
}}
prefixPairRDD.foreach(println)
println("-------------开始局部聚合-------")
val partAggrRDD:RDD[(String, Int)] = prefixPairRDD.reduceByKey(_+_)
partAggrRDD.foreach(println)
/开始全局聚合
println("/开始全部聚合/")
val unPrefixRDD = partAggrRDD.map{case (word, count) => {
if(word.contains("_")) {
(word.substring(word.indexOf("_") + 1), count)
} else {
(word, count)
}
}}
println("--------去掉局部聚合前缀的结果:------------")
unPrefixRDD.foreach(println)
println("-------------执行全局聚合-------")
unPrefixRDD.reduceByKey(_+_).foreach(println)
sc.stop()
}
}
3.
package com.spark.optimization.p2
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* sparkrdd的广播变量使用:
* 广播变量不适合以下两种情况:
* 1、数据集体积比较大,超过1G不建议使用,broadcast避免不了数据的传输和备份
* 2、频繁更新的数据不建议设置为广播变量
* 说明:
* 如果跨阶段stage执行的广播变量,这个时候广播变量的持久化是非常重要的,同时在使用之前需要进行反序列化。
* 而如果只在一个阶段中调用了该广播变量(窄依赖),这个时候为了提交作业效率,便不需要进行序列化。
*
* 表之间的关联使用:
* student表
* id name age gid
*
*
* gender
* id gender
* 0 女
* 1 男
* 做两张表的关联:
* 将student表中的gid替换成gender表中的gender
* 不用上节课学习的join算子完成表之间的关联操作。
*
*
* 通过学习发现,广播变量+map算子可以完成的join,把这种join我们称之为map join(等同于mapreduce中的mapjoin),这个时候没有shuffle,但是限于大小表关联
* 而join操作被称之为reduce join,是存在shuffle操作的。
*
* map join完全避免了shuffle操作,也就一定不会出现数据倾斜,但是需要唯一注意的一点就是:
* 需要将小表全量加载到内存,需要的资源比较大,有可能会OOM。
*/
object _03SparkRDDBroadcastOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setMaster("local[*]")
.setAppName(s"${_03SparkRDDBroadcastOps.getClass.getSimpleName}")
val sc = new SparkContext(conf)
val list = List(
"001 刘晓飞 30 1",
"002 刘森 33 0",
"003 赵李明 28 1",
"004 oldLi 23 2"
)
val gender = List(
"0 女",
"1 男"
)
val personRDD:RDD[String] = sc.parallelize(list)
personRDD.cache()
//今天之前的操作,完成两张表的关联操作 join
val genderRDD:RDD[String] = sc.parallelize(gender)
val gid2Person = personRDD.map(line => {
val fields = line.split("\\s+")
val gid = fields(3)
val other = fields(0) + "," + fields(1) + "," + fields(2)
(gid, other)
})
val gid2Gender = genderRDD.map(line => {
val fields = line.split("\\s+")
(fields(0), fields(1))
})
//reduce join操作
val joinInfo:RDD[(String, (String, String))] = gid2Person.join(gid2Gender)
joinInfo.foreach{case (gid, (person, gender)) => {
println(s"${person},${gender}")
}}
println("----------------使用广播变量的方式来做多表关联--------------------")
//构建广播变量
val genderMap = gender.map(line => {
val fields = line.split("\\s+")
(fields(0), fields(1))
}).toMap
//将小表采用广播变量的方式加载到内存
val genderBC:Broadcast[Map[String, String]] = sc.broadcast(genderMap)
//多表关联
//"001 刘晓飞 30 1"
val pRDD:RDD[String] = personRDD.map(line => {
val genderBCMap = genderBC.value
val gid = line.substring(line.lastIndexOf(" ") + 1)
//map join
val gender = genderBCMap.getOrElse(gid, "ladyboy")
line.substring(0, line.lastIndexOf(" ")) + " " + gender
})
pRDD.foreach(println)
sc.stop()
}
}
4.
package com.spark.optimization.p2
import java.util.Random
import org.apache.log4j.{Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable.ArrayBuffer
/**
* 采样倾斜key并分拆join操作
* 两个RDD(rdd1正常,rdd2有少量数据的倾斜),要完成二者的join(rdd1.join(rdd2))
* 1、对rdd1和rdd2进行拆分(使用的之前的确定发生数据倾斜的key进行拆分),分别转化为两个RDD
* rdd1--->
* ds_rdd1
* common_rdd1
* rdd2--->
* ds_rdd2
* common_rdd2
* 2、对ds_rdd2进行拆分打散,添加N以内的随机前缀,转化为prefix_ds_rdd2
* 以及对ds_rdd1进行添加N以内的前缀,进行扩容N倍prefix_ds_rdd1
* 3、分别对有有倾斜的rdd和正常的rdd进行join操作
* prefix_ds_rdd2.join(prefix_ds_rdd1)=dsPrefixJoinRDD
* common_rdd1.join(common_rdd2)=commonJoinRDD
* 4、去除dsPrefixJoinRDD前缀---->dsJoinRDD
* 5、做两次joinRDD的union得到最后的结果
* dsJoinRDD.union(commonJoinRDD)=finalRDD
*/
object _04SplitJoinOps {
def main(args: Array[String]): Unit = {
Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.spark-project").setLevel(Level.WARN)
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName(s"${_02TwoStageAggrDataSkewOps.getClass.getSimpleName}")
val sc = new SparkContext(conf)
//加载数据
val list1 = List(
"hello you", "hello me", "hello he"
)
val list2 = List(
"hello you", "hello me", "hello hello hello hello hello hello", "hello you", "hello me"
)
val listRDD1 = sc.parallelize(list1)
val rdd1 = listRDD1.flatMap(_.split("\\s+")).map((_, 1))
println("rdd1中的数据:")
rdd1.foreach(println)
val listRDD2 = sc.parallelize(list2)
val rdd2 = listRDD2.flatMap(_.split("\\s+")).map((_, 1))
println("rdd2中的数据:")
rdd2.foreach(println)
println(">>>>>>>>>>>>>>>>>数据倾斜的key的定位<<<<<<<<<<<<")
val dataSkewKeys = getDataSkewKey(rdd2)
println("发生数据倾斜的key为:" + dataSkewKeys.mkString("[", ",", "]"))
//step1 进行数据的分拆操作
println("step 1、将两个rdd按照数据倾斜的key分别转化为对应的两个rdd")
val commonRdd1 = rdd1.filter{case (word, count) => {
!dataSkewKeys.contains(word)
}}
val dsRdd1 = rdd1.filter{case (word, count) => {
dataSkewKeys.contains(word)
}}
println("rdd1拆分为正常commonRdd1的数据结果:")
commonRdd1.foreach(println)
println("rdd1拆分为异常dsRdd1的数据结果:")
dsRdd1.foreach(println)
val commonRdd2 = rdd2.filter{case (word, count) => {
!dataSkewKeys.contains(word)
}}
val dsRdd2 = rdd2.filter{case (word, count) => {
dataSkewKeys.contains(word)
}}
println("rdd2拆分为正常commonRdd1的数据结果:")
commonRdd2.foreach(println)
println("rdd2拆分为异常dsRdd1的数据结果:")
dsRdd2.foreach(println)
// step 2添加随机前缀
println("step 2、分别给dsRDD1和dsRDD2添加对应的前缀")
val random = new Random()
val prefixDSRDD2 = dsRdd2.map{case (word, count) => {//对不正常的RDD添加随机前缀
val prefix = random.nextInt(2)
(s"${prefix}_${word}", count)
}}
val prefixDSRDD1 = dsRdd1.flatMap{case (word, count) => {
val ab = ArrayBuffer[(String, Int)]()
for (i <- 0 until 2) {
ab.append((s"${i}_${word}", count))
}
ab.iterator
}}
println("对异常dsRdd2的拆分之后的结果:")
prefixDSRDD2.foreach(println)
println("对异常dsRdd1的扩容之后的结果:")
prefixDSRDD1.foreach(println)
//step 3 分别对有有倾斜的rdd和正常的rdd进行join操作
println("step 3、分别对有有倾斜的rdd和正常的rdd进行join操作")
//正常commonRDD的join
val commonJoinRDD = commonRdd1.join(commonRdd2)
//有倾斜的rdd的join
val prefixJoinDSRDD = prefixDSRDD1.join(prefixDSRDD2)
println("正常rdd进行join之后的结果")
commonJoinRDD.foreach(println)
println("异常常rdd进行join之后的结果")
prefixJoinDSRDD.foreach(println)
//step 4 去掉异常rddjoin之后结果的前缀
println("step 4 去掉异常rddjoin之后结果的前缀")
val joinDSRDD = prefixJoinDSRDD.map{case (word, count) => {
(word.substring(word.indexOf("_") + 1), count)
}}
joinDSRDD.foreach(println)
//step 5、做两次joinRDD的union得到最后的结果
println("step 5、做两次joinRDD的union得到最后的结果")
val finalRDD = commonJoinRDD.union(joinDSRDD)
println("最终的聚合结果:")
finalRDD.foreach(println)
sc.stop()
}
private def getDataSkewKey(rdd:RDD[(String, Int)]):Array[String] = {
val sampleRDD = rdd.sample(false, 0.4)
val sampleRBKRDD:RDD[(String, Int)] = sampleRDD.reduceByKey(_+_)
val top1:Array[(String, Int)] = sampleRBKRDD.takeOrdered(1)(new Ordering[(String, Int)]() {
override def compare(x: (String, Int), y: (String, Int)) = {
y._2.compareTo(x._2)
}
})
println("--------------------->可能发生数据倾斜的可以为:")
top1.foreach(println)
val dataSkewKeys:Array[String] = top1.map{case (word, count) => word}
dataSkewKeys
}
}
5.
package com.spark.optimization.p2
import java.util.Random
import org.apache.log4j.{Level, Logger}
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable.ArrayBuffer
/**
* 使用随机前缀和扩容RDD进行join
* 是对上一个案例的补充,
* 适合于两个RDD进行join,其中一个正常,另外一个RDD中有多个key都发生倾斜的
* 思路:
* 1、对倾斜的rdd进行添加N以内的随机前缀
* 2、对正常的RDD进行全量扩容
* 3、进行join
* 4、去掉随机前缀
* 需要注意一点的是:该方案对资源要求非常高
*/
object _05RandomPrefixJoinOps {
def main(args: Array[String]): Unit = {
Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.spark-project").setLevel(Level.WARN)
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName(s"${_05RandomPrefixJoinOps.getClass.getSimpleName}")
val sc = new SparkContext(conf)
//加载数据
val list1 = List(
"hello you", "hello me", "hello he"
)
val list2 = List(
"hello you", "hello me", "hello hello hello hello hello hello", "hello you", "hello me"
)
val listRDD1 = sc.parallelize(list1)
val rdd1 = listRDD1.flatMap(_.split("\\s+")).map((_, 1))
println("rdd1中的数据:")
rdd1.foreach(println)
val listRDD2 = sc.parallelize(list2)
val rdd2 = listRDD2.flatMap(_.split("\\s+")).map((_, 1))
println("rdd2中的数据:")
rdd2.foreach(println)
println("step 1、对倾斜的rdd进行添加N以内的随机前缀 ")
val random = new Random()
val randomPrefixRDD = rdd2.map { case (word, count) => {
val prefix = random.nextInt(3)
(s"${prefix}_${word}", count)
}}
randomPrefixRDD.foreach(println)
println("step 2、对正常的RDD进行全量扩容")
val extendsPrefixRDD = rdd1.flatMap{case (word, count) => {
val ab = ArrayBuffer[(String, Int)]()
for(i <- 0 until 3) {
ab.append((s"${i}_${word}", count))
}
ab.iterator
}}
extendsPrefixRDD.foreach(println)
println("step 3、进行join")
val prefixJoinRDD = randomPrefixRDD.join(extendsPrefixRDD)
prefixJoinRDD.foreach(println)
println("step 4、移除前缀")
val joinRDD = prefixJoinRDD.map { case (word, count) => {
(word.substring(word.indexOf("_") + 1), count)
}
}
joinRDD.foreach(println)
sc.stop()
}
}