Spark---数据倾斜一些代码测试

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()
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值