《深入理解Spark》之join和数据倾斜问题

本文详细探讨了Spark中的join操作,包括不同类型的join及其实现原理。同时,重点分析了数据倾斜问题的原因、影响及解决方案,为优化大规模数据处理提供关键指导。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

package com.lyzx.day34

import org.apache.spark._

class T2 {
  /**
    * join方法深入理解
    * join类似于sql中的inner join会把键完全匹配的项列以(key,(v1,v2))的形式列出来
    * leftOuterJoin 类似于SQL中的left outer join 把左表的全部列出来,右表中不匹配的用None表示
    *   (3,(3,Some(3))),(4,(4,Some(4))),(1,(1,None))
    *
    * @param sc
    */
  def f1(sc:SparkContext): Unit ={
    val rdd1 = sc.parallelize(Array(1,2,3,4,5))
    val rdd2 = sc.parallelize(Array(4,5,6,7))

    val pairRdd1 = rdd1.map(x=>(x,x))
    val pairRdd2 = rdd2.map(x=>(x,x))

//    pairRdd1.join(pairRdd2)
//              .foreach(println)

    pairRdd1.leftOuterJoin(pairRdd2)
              .foreach(println)
  }


  /**
    * 对于join之前进行co-partition和不进行co-partition的效率测试
    * 实测join之前进行co-partition操作的效率高于直接join
    * 注意:数据量小的时候情况不一定
    * @param sc
    */
  def f2(sc:SparkContext): Unit ={

    //产生两个RDD
    val rdd1 = sc.parallelize(1 to 18,3)
    val rdd2 = sc.parallelize(13 to 30,3)

    //映射RDD
    val pairRdd1 = rdd1.map(x=>(x,x))
    val pairRdd2 = rdd2.map(x=>(x,x))

    //查看每个分区里的数据,调试时使用
//    pairRdd1.mapPartitionsWithIndex((index,itr)=>{
//      println("index1="+index)
//      while(itr.hasNext){
//        print("-"+itr.next())
//      }
//      println()
//      for(v <- itr) yield v
//    }).collect()
//
//    pairRdd2.mapPartitionsWithIndex((index,itr)=>{
//      println("index2="+index)
//      while(itr.hasNext){
//        print("="+itr.next())
//      }
//      for(v <- itr) yield v
//    }).collect()

    val start1 = System.currentTimeMillis()

    //直接做join并记录时间
    println("start1="+start1)
    val joinRDD = pairRdd1.join(pairRdd2)
              .mapPartitionsWithIndex((index,itr)=>{
                  println("pairRDD1,Index:"+index)
                  while(itr.hasNext){
                    print("-"+itr.next())
                  }
                  for(v <- itr) yield  v
              })
//      .foreach(x=>print())
    println("joinRDD.partitions.length:"+joinRDD.partitions.length)
    val end1 = System.currentTimeMillis()
    println("耗时="+(end1-start1))  //2563
    val start2 = System.currentTimeMillis()
    val group1 = pairRdd1.groupByKey()
    val group2 = pairRdd2.groupByKey()

//    println("#############################")
    // 查看数据,调试使用
    group1.mapPartitionsWithIndex((index,itr)=>{
      println("group1-index1="+index)
      while(itr.hasNext){
        print("-"+itr.next())
      }
      println()
      for(v <- itr) yield v
    }).collect()

    group2.mapPartitionsWithIndex((index,itr)=>{
      println("group2-index1="+index)
      while(itr.hasNext){
        print("="+itr.next())
      }
      println()
      for(v <- itr) yield v
    }).collect()

    println("start2="+start2)
    group1.join(group2)
      .mapPartitionsWithIndex((index,itr)=>{
        println("pairRDD2,Index:"+index)
        while(itr.hasNext){
          print("="+itr.next())
        }
        for(v <- itr) yield  v
      }).foreach(x=>print())

    val end2 = System.currentTimeMillis()
    println("耗时:"+(end2-start2)) //2000
  }


  /**
    * partitionBy的使用
    * @param sc
    */
  def f3(sc:SparkContext): Unit ={
    val rdd1 = sc.parallelize(1 to 40,4).map(x=>(x,x))
    rdd1
        .mapPartitionsWithIndex((index,itr)=>{
          print("index:"+index)
          while(itr.hasNext){
            print(" "+itr.next())
          }
          println()
          for(v <- itr) yield  v
        })
      .foreach(x=>print("-"+x))

    println("###################################")
    rdd1.partitionBy(new org.apache.spark.HashPartitioner(rdd1.partitions.length))
      .mapPartitionsWithIndex((index,itr)=>{
        print("index:"+index)
        while(itr.hasNext){
          print(" "+itr.next())
        }
        println()
        for(v <- itr) yield  v
      })
      .foreach(x=>print("-"+x))
  }


  /**
    * 关于Spark的并行度的问题
    * 修改并行度的方式
    * 方法1:直接在代码中以local[*]的形式
    * 方法2:conf.set("spark.default.parallelism","4")
    * 方法3:val rdd = sc.parallelize(10 to 200,numPartitions)
    * 优先级: 方法3>方法2>方法1
    * @param sc
    */
  def f4(sc:SparkContext): Unit ={
    val rdd = sc.parallelize(10 to 200)
    println(rdd.partitions.length)
  }


  /**
    * 关于filter后数据倾斜的问题
    * 如果在使用filter算子后发现数据倾斜问题
    * 那么可以通过将RDD[K]=>RDD[K,V]形式的RDD在使用默认的 HashPartitioner进行分区后
    * 在使用map算子转换回来
    *
    * 如果RDD[T] 其中T是自定义对象可以使用自定义分区器进行分区
    * 注意:网上有人说使用filter+coalesce算子可以解决问题
    *     经过我实际测试发现并不能完全解决数据倾斜问题
    *
    * @param sc
    */
  def f5(sc:SparkContext): Unit ={
    val rdd = sc.parallelize(1 to 30,5).cache()

    rdd.mapPartitionsWithIndex((index,itr)=>{
                   println("index1:"+index+" ")
                   while(itr.hasNext){
                     val line = itr.next()
                       print("<|"+index+">"+line)
                   }
                   println()
                   for(v <- itr) yield v
               })

    rdd
        .filter(_>10)
        .map(x=>(x,1))
        .partitionBy(new HashPartitioner(3))
        .map(x=>x._1)
        .mapPartitionsWithIndex((index,itr)=>{
                               println("index2:"+index+" ")
                               while(itr.hasNext){
                                 val line = itr.next()
                                   print("<||"+index+">"+line)
                               }
                               println()
                               for(v <- itr) yield v
                             })
        .collect()
  }

  /**
    * 使用自定义的分区器对数据进行分区
    * @param sc
    */
  def f6(sc:SparkContext): Unit ={
    val r1 = "lyzx,10,1"
    val r2 = "lyzx,11,2"
    val r3 = "lyzx,12,3"
    val r4 = "lyzx,13,4"
    val r5 = "lyzx,14,5"
    val r6 = "lyzx,15,6"
    val r7 = "lyzx,16,7"
    val r8 = "lyzx,17,8"
    val r9 = "lyzx,18,9"
    val r10 = "lyzx,19,10"
    val r11 = "lyzx,20,11"

    val arr = Array(r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11)
    val rdd = sc.parallelize(arr,5)

    val pRdd= rdd.map(x=>x.split(","))
                 .map(x=>new Person(x(0),x(1).toInt,x(2).toInt))

//    pRdd.mapPartitionsWithIndex((index,itr)=>{
//                    while(itr.hasNext){
//                      println("["+index+"]  "+itr.next())
//                    }
//                    for(v <- itr) yield  v
//                }).collect()

      pRdd
          .filter(x=>x.age > 15)
          .map(x=>(x,1))
          .partitionBy(new No_1_Partitioner(3))
          .map(x=>x._1)
          .mapPartitionsWithIndex((index,itr)=>{
              while(itr.hasNext){
                println("<"+index+">  "+itr.next())
              }
              for(v <- itr) yield  v
          })
          .collect()
  }
}

object T2{
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("day34").setMaster("local")
    conf.set("spark.default.parallelism","4")
    val sc = new SparkContext(conf)

    val t = new T2
//    t.f1(sc)
//    t.f2(sc)
//    t.f3(sc)
//    t.f4(sc)
//    t.f5(sc)
    t.f6(sc)

    sc.stop()
  }
}

case class Person(name:String,var age:Int,id:Int){

  override def toString: String = {
    "[name="+name+",age="+age+",id="+id+"]"
  }
}


class No_1_Partitioner(num:Int) extends  Partitioner{
  override def numPartitions: Int = num
  println("....No_1_Partitioner["+num+"]被创建......")

  //按照年龄%num来分区,当然在实际使用中还是以业务为准
  override def getPartition(key: Any): Int = {
    val p = key.asInstanceOf[Person]
    val pr = p.age % num
    print("========"+p.age+"%"+num+"="+pr)
    pr
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值