spark rdd之groupByKey

源码

  • groupByKey
  def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])] = self.withScope {
  	//定义聚合方法
    //前两个方法都是在map side执行的
    //将每组内的第一个Value放到CompactBuffer容器内
    val createCombiner = (v: V) => CompactBuffer(v)
    //将每组内的2-n个元素加入到CompactBuffer容器内
    val mergeValue = (buf: CompactBuffer[V], v: V) => buf += v

    //第三个函数 是在全局执行的,是在shuffledRead之后执行的
    //分区间合并相同的CompactBuffer
    val mergeCombiners = (c1: CompactBuffer[V], c2: CompactBuffer[V]) => c1 ++= c2
    val bufs = combineByKeyWithClassTag[CompactBuffer[V]](
      createCombiner, mergeValue, mergeCombiners, partitioner, mapSideCombine = false)
    bufs.asInstanceOf[RDD[(K, Iterable[V])]]
  }

此时我们看到底层调用的是 combineByKeyWithClassTag,传入3个函数

  • combineByKeyWithClassTag
def combineByKeyWithClassTag[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C,
      partitioner: Partitioner,
      mapSideCombine: Boolean = true,
      serializer: Serializer = null)(implicit ct: ClassTag[C]): RDD[(K, C)] = self.withScope {
    require(mergeCombiners != null, "mergeCombiners must be defined") // required as of Spark 0.9.0
    if (keyClass.isArray) {
      if (mapSideCombine) {
        throw new SparkException("Cannot use map-side combining with array keys.")
      }
      if (partitioner.isInstanceOf[HashPartitioner]) {
        throw new SparkException("HashPartitioner cannot partition array keys.")
      }
    }
    val aggregator = new Aggregator[K, V, C](
      self.context.clean(createCombiner),
      self.context.clean(mergeValue),
      self.context.clean(mergeCombiners))
      
      // 判断父子分区器是否一致,一致则直接使用mapPartitions,此时不会产生宽依赖
    if (self.partitioner == Some(partitioner)) {
      self.mapPartitions(iter => {
        val context = TaskContext.get()
        new InterruptibleIterator(context, aggregator.combineValuesByKey(iter, context))
      }, preservesPartitioning = true)
    } 
    // 父子分区器不一致,new ShuffledRDD
    else{
      new ShuffledRDD[K, V, C](self, partitioner)
        .setSerializer(serializer)
        .setAggregator(aggregator)
        .setMapSideCombine(mapSideCombine)
    }
  }

combinerbykey()要经历三个阶段
第一个阶段是第一条记录的处理,第二个阶段是第二条及之后的记录的处理,第三个阶段是合并之前溢写出来多个小文件的处理。

这也是combinerbykey()要传入三个函数的原因。
这个函数将三个函数封装进一个aggregator里面,在函数的最后,会 new 一个ShuffledRDD,
并调用setSerializer(),setAggregator(),setMapSideCombine()三个方法,
第一个方法是序列化,第二个方法是聚合操作,即执行传入的三个方法,第三个是设置map端聚合。

图解

前两个方法都是在map side执行的
createCombiner:创建一个Combiner,就是将每一个组内的第一个Value加到ArrayBuffer内
mergeValue:将组内其他的value依次遍历加到ArrayBuffer内

mergeCombiners :第三个函数 是在全局执行的,是在shuffledRead之后执行的

在这里插入图片描述

示例

val rdd1: RDD[String] = sc.parallelize(List(
      "spark", "hadoop", "hive", "spark",
      "spark", "flink", "hive", "spark",
      "kafka", "kafka", "kafka", "kafka",
      "hadoop", "flink", "hive", "flink"
    ), 4)
    //todo 1.groupByKey用法
    rdd1.map((_, 1)).groupByKey()
      .mapPartitionsWithIndex((index, iter) => {
        iter.map((index, _))
      }).foreach(println)

用shuffleRdd实现GroupByKey

 val rdd1: RDD[String] = sc.parallelize(List(
      "spark", "hadoop", "hive", "spark",
      "spark", "flink", "hive", "spark",
      "kafka", "kafka", "kafka", "kafka",
      "hadoop", "flink", "hive", "flink"
    ), 4)
    
 //todo  使用ShuffledRdd去实现groupByKey
    val workAndOne: RDD[(String, Int)] = rdd1.map((_, 1))

    val shuffledRdd: ShuffledRDD[String, Int, ArrayBuffer[Int]] = new ShuffledRDD[String, Int, ArrayBuffer[Int]](
      workAndOne, new HashPartitioner(workAndOne.getNumPartitions)
    )//传入父RDD 和 分区器

    shuffledRdd.setMapSideCombine(false)//关闭map端的预聚合
    //定义聚合方法
    //前两个方法都是在map side执行的
    //创建一个Combiner:就是将每一个组内的第一个Value加到ArrayBuffer内
    val createCombiner: Int => ArrayBuffer[Int] = (a:Int)=>ArrayBuffer(a)
    //将组内其他的value依次遍历加到ArrayBuffer内
    val mergeValue =(ab:ArrayBuffer[Int],c:Int)=>ab += c

    //第三个函数 是在全局执行的,是在shuffledRead之后执行的
    val mergeCombiners = (ab1:ArrayBuffer[Int],ab2:ArrayBuffer[Int])=>ab1++=ab2
    
    shuffledRdd.setAggregator(new Aggregator[String,Int,ArrayBuffer[Int]](
      createCombiner,
      mergeValue,
      mergeCombiners
    )).mapPartitionsWithIndex((index,iter)=>{
      iter.map((index,_))
    }).foreach(println)

注意事项

GroupByKey操作可能非常昂贵。如果您正在分组以便对每个键执行聚合(例如求和或平均),则使用 aggregateByKey reduceByKey 将提供更好的性能。
注意:按照目前的实现,groupByKey 必须能够保存内存中任何键的所有键值对。如果一个键的值太多,造成数据倾斜,可能会导致 OutOfMemoryError

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值