Spark源码-spark算子-2-shuffle类算子

26 篇文章 0 订阅
25 篇文章 2 订阅

1.概述

说明:当前spark源码分析基于spark版本2.11进行;

2.去重算子

2.1.distinct

  1. RDD的去重算子调用,可以指定分区数,也可以不指定分区数;

  2. 当不指定分区数时,使用调用RDD的分区数作为distinct后RDD的分区数;

  3. distinct()算子底层通过调用 distinct(numPartitions: Int)实现去重效果;

  4. distinct通过reduceByKey分组,然后提取分组的key实现去重效果;

  5. distinct算子的shuffle发生在reduceByKey阶段;

abstract class RDD[T: ClassTag](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]]
  ) extends Serializable with Logging {
	
  //通过reduceByKey分组,然后提取分组后的key值实现去重
  def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
    map(x => (x, null)).reduceByKey((x, y) => x, numPartitions).map(_._1)
  }

  //使用调用RDD的分区数作为distinct后的RDD的分区数
  def distinct(): RDD[T] = withScope {
    distinct(partitions.length)
  }
}

3.聚合算子

常用聚合算子的区别:

reduceByKeygroupByKeyaggregateByKeycombineByKey
支持map端聚合
指定分组内聚合初始值
指定创建分组内聚合初始值的函数
指定分组内聚合方式的函数
指定分组间聚合方式的函数

3.1.复用性函数

3.1.1.默认分区器

默认分区器选择:

  1. 如果RDD现有的最大分区器是合格的,或者它的分区数大于默认的分区数,则使用现有的分区器;
  2. 否则以默认分区数构建hash分区器;

默认分区数确定:

  1. 配置文件有设置默认分区数参数spark.default.parallelism,取默认分区数
  2. 否则使用rdd中最大分区数;
class PairRDDFunctions[K, V](self: RDD[(K, V)])
    (implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null)
  extends Logging with Serializable {
    
  def defaultPartitioner(rdd: RDD[_], others: RDD[_]*): Partitioner = {
    val rdds = (Seq(rdd) ++ others)
    //过滤分区数大于0的分区器
    val hasPartitioner = rdds.filter(_.partitioner.exists(_.numPartitions > 0))

    //分区数最大的分区器
    val hasMaxPartitioner: Option[RDD[_]] = if (hasPartitioner.nonEmpty) {
      Some(hasPartitioner.maxBy(_.partitions.length))
    } else {
      None
    }

    //默认分区数:配置文件有设置默认分区数参数,取默认分区数;否则使用rdd中最大分区数
    val defaultNumPartitions = if (rdd.context.conf.contains("spark.default.parallelism")) {
      rdd.context.defaultParallelism
    } else {
      rdds.map(_.partitions.length).max
    }

    // 如果现有的最大分区器是合格的,或者它的分区数目大于默认的分区数目,则使用现有的分区器,否则以默认分区数构建hash分区器
    if (hasMaxPartitioner.nonEmpty && (isEligiblePartitioner(hasMaxPartitioner.get, rdds) ||
        defaultNumPartitions < hasMaxPartitioner.get.getNumPartitions)) {
      hasMaxPartitioner.get.partitioner.get
    } else {
      new HashPartitioner(defaultNumPartitions)
    }
  }
}

3.1.1.combineByKeyWithClassTag

  1. 默认支持map端聚合;(mapSideCombine默认值为true,当调用combineByKeyWithClassTag时如果不传mapSideCombine,默认值即生效)
  2. 当参数指定分区器时,使用指定分区器;
  3. 当参数指定分区数时,使用hash分区器,分区数根据参数确定;
  4. 当参数没有指定分区器,没有指定分区数时,使用默认分区器;
class PairRDDFunctions[K, V](self: RDD[(K, V)])
    (implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null)
  extends Logging with Serializable {
  
  //使用默认分区器,默认支持map端聚合
  def combineByKeyWithClassTag[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C)(implicit ct: ClassTag[C]): RDD[(K, C)] = self.withScope {
    combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners, defaultPartitioner(self))
  }
  
  //使用hash分区器,默认支持map端聚合
  def combineByKeyWithClassTag[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C,
      numPartitions: Int)(implicit ct: ClassTag[C]): RDD[(K, C)] = self.withScope {
    combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners,
      new HashPartitioner(numPartitions))
  }
   
  //使用指定分区器,默认支持map端聚合
  def combineByKeyWithClassTag[C](
      createCombiner: V => C,				//函数用于创建聚合的初始值
      mergeValue: (C, V) => C,			//将新值合并到聚合结果中的函数
      mergeCombiners: (C, C) => C,	//用于合并多个mergeValue函数的输出的函数
      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))
    
    //判断调用combineByKeyWithClassTag的RDD的分区器和参数分区器是否一致
    if (self.partitioner == Some(partitioner)) {
      //构建MapPartitionsRDD
      self.mapPartitions(iter => {
        val context = TaskContext.get()
        new InterruptibleIterator(context, aggregator.combineValuesByKey(iter, context))
      }, preservesPartitioning = true)
    } else {
      //构建ShuffledRDD,指定数据聚合器、map端聚合标识
      new ShuffledRDD[K, V, C](self, partitioner)
        .setSerializer(serializer)
        .setAggregator(aggregator)
        .setMapSideCombine(mapSideCombine)
    }
  }
}

3.2.reduceByKey

分区器的确定:

  1. 分区原理同combineByKeyWithClassTag大同小异;

底层实现原理:

  1. reduceByKey算子最后都是通过reduceByKey(partitioner: Partitioner, func: (V, V) => V)底层调用combineByKeyWithClassTag实现;
  2. 调用时制指定createCombiner、mergeValue、mergeCombiners、partitioner;

特别说明:

  1. reduceByKey支持map端聚合;原因见combineByKeyWithClassTag分析;
  2. 分组内外聚合方式一致,调用函数时必须指定;
class PairRDDFunctions[K, V](self: RDD[(K, V)])
    (implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null)
  extends Logging with Serializable {
    
  //使用指定分区器,底层调用combineByKeyWithClassTag实现
  def reduceByKey(partitioner: Partitioner, func: (V, V) => V): RDD[(K, V)] = self.withScope 	 {
    combineByKeyWithClassTag[V]((v: V) => v, func, func, partitioner)
  }

  //使用hash分区器
  def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)] = self.withScope {
    reduceByKey(new HashPartitioner(numPartitions), func)
  }

  //使用默认分区器
  def reduceByKey(func: (V, V) => V): RDD[(K, V)] = self.withScope {
    reduceByKey(defaultPartitioner(self), func)
  }
}

3.3.groupByKey

分区器的确定:

  1. 分区原理同combineByKeyWithClassTag大同小异;

底层实现原理:

  1. groupByKey算子最后都是通过groupByKey(partitioner: Partitioner)底层调用combineByKeyWithClassTag实现;调用时指定不支持map端聚合;

特别说明:

  1. groupByKey与reduceByKey的区别在是否支持map端聚合:groupByKey不支持,reduceByKey支持;
  2. reduceByKey需要指定分组内外聚合方式,groupByKey默认累加,不需要调用时指定;
class PairRDDFunctions[K, V](self: RDD[(K, V)])
    (implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null)
  extends Logging with Serializable {
    
  //底层通过combineByKeyWithClassTag实现,默认不支持map端聚合
  def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])] = self.withScope {
    
    val createCombiner = (v: V) => CompactBuffer(v)
    val mergeValue = (buf: CompactBuffer[V], v: V) => buf += v
    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])]]
  }
    
  //使用hash分区器
  def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])] = self.withScope {
    groupByKey(new HashPartitioner(numPartitions))
  }
    
  //使用默认分区器
  def groupByKey(): RDD[(K, Iterable[V])] = self.withScope {
    groupByKey(defaultPartitioner(self))
  }
}

3.4.groupBy

  1. 分区原理同combineByKeyWithClassTag大同小异;
  2. 底层调用groupByKey实现结果聚合;
  3. 分组方式由groupBy调用参数指定;
abstract class RDD[T: ClassTag](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]]
  ) extends Serializable with Logging {
  
  //使用默认分区器
  def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])] = withScope {
    groupBy[K](f, defaultPartitioner(this))
  }

  //使用hash分区器:分区数参数指定
  def groupBy[K](
      f: T => K,
      numPartitions: Int)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])] = withScope {
    groupBy(f, new HashPartitioner(numPartitions))
  }

  //由分组函数指定分组key,最后由groupByKey实现聚合(指定分区器)
  def groupBy[K](f: T => K, p: Partitioner)(implicit kt: ClassTag[K], ord: Ordering[K] = null)
      : RDD[(K, Iterable[T])] = withScope {
    val cleanF = sc.clean(f)
    this.map(t => (cleanF(t), t)).groupByKey(p)
  }
}

3.5.aggregateByKey

总结:

  1. 分区原理同combineByKeyWithClassTag大同小异;
  2. 底层调用combineByKeyWithClassTag实现结果聚合;
  3. 需要指定每个分组聚合的初始值;
  4. 默认支持map端聚合;

特别说明:

  1. aggregateByKey同reduceByKey的区别:
    • aggregateByKey需要指定每个分组聚合的初始值,reduceByKey不需要;
    • aggregateByKey需要指定分组内外2种聚合方式,reduceByKey只需要指定一种,内外通用;
  2. aggregateByKey同groupByKey的区别:
    • aggregateByKey需要指定每个分组聚合的初始值,groupByKey不需要;
    • aggregateByKey支持map端聚合,groupByKey不支持;
    • aggregateByKey需要指定分组内外2种聚合方式,groupByKey不需要指定;
abstract class RDD[T: ClassTag](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]]
  ) extends Serializable with Logging {
  
  //zeroValue:每个分组的初始值
  //seqOp:分组内聚合方式
  //combOp:分组间聚合方式
  def aggregateByKey[U: ClassTag](zeroValue: U, partitioner: Partitioner)(seqOp: (U, V) => U,
      combOp: (U, U) => U): RDD[(K, U)] = self.withScope {
    // Serialize the zero value to a byte array so that we can get a new clone of it on each key
    val zeroBuffer = SparkEnv.get.serializer.newInstance().serialize(zeroValue)
    val zeroArray = new Array[Byte](zeroBuffer.limit)
    zeroBuffer.get(zeroArray)

    lazy val cachedSerializer = SparkEnv.get.serializer.newInstance()
    val createZero = () => cachedSerializer.deserialize[U](ByteBuffer.wrap(zeroArray))

    // We will clean the combiner closure later in `combineByKey`
    val cleanedSeqOp = self.context.clean(seqOp)
    //调用combineByKeyWithClassTag算子
    combineByKeyWithClassTag[U]((v: V) => cleanedSeqOp(createZero(), v),
      cleanedSeqOp, combOp, partitioner)
  }
  
  //使用hash分区器:分区数参数指定
  def aggregateByKey[U: ClassTag](zeroValue: U, numPartitions: Int)(seqOp: (U, V) => U,
      combOp: (U, U) => U): RDD[(K, U)] = self.withScope {
    aggregateByKey(zeroValue, new HashPartitioner(numPartitions))(seqOp, combOp)
  }
  
  //使用默认分区器
  def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U,
      combOp: (U, U) => U): RDD[(K, U)] = self.withScope {
    aggregateByKey(zeroValue, defaultPartitioner(self))(seqOp, combOp)
  }
}

3.6.combineByKey

  1. 默认支持map端聚合;
  2. 调用函数时必须指定createCombiner、mergeValue、mergeCombiners;
  3. 底层调用combineByKeyWithClassTag实现;
abstract class RDD[T: ClassTag](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]]
  ) extends Serializable with Logging {
  
  //使用默认分区器,默认支持map端聚合
  def combineByKey[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C): RDD[(K, C)] = self.withScope {
    combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners)(null)
  }
  
  //使用hash分区器,默认支持map端聚合
  def combineByKey[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C,
      numPartitions: Int): RDD[(K, C)] = self.withScope {
    combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners, numPartitions)(null)
  } 
  
  //使用指定分区器,默认支持map端聚合
  def combineByKey[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C,
      partitioner: Partitioner,
      mapSideCombine: Boolean = true,
      serializer: Serializer = null): RDD[(K, C)] = self.withScope {
    combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners,
      partitioner, mapSideCombine, serializer)(null)
  }
}

4.排序算子

4.1.sortByKey

  1. 支持设置排序方式以及分区数;默认升序排列、使用调用RDD的分区数;
  2. 分区器为RangePartitioner;
  3. 构建ShuffleRDD,设置排序方式、分区器;
class OrderedRDDFunctions[K : Ordering : ClassTag,
                          V: ClassTag,
                          P <: Product2[K, V] : ClassTag] @DeveloperApi() (
    self: RDD[P])
  extends Logging with Serializable {
    
  //ascending:升序;默认升序;
	def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
      : RDD[(K, V)] = self.withScope
  {
    //分区器:RangePartitioner
    val part = new RangePartitioner(numPartitions, self, ascending)
    //构建ShuffleRDD,设置排序方式
    new ShuffledRDD[K, V, V](self, part)
      .setKeyOrdering(if (ascending) ordering else ordering.reverse)
  }    
}

4.2.sortBy

  1. 需要指定排序依据:根据什么进行排序;
  2. 底层调用sortByKey执行;
  3. 支持设置排序方式以及分区数;默认升序排列、使用调用RDD的分区数;
abstract class RDD[T: ClassTag](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]]
  ) extends Serializable with Logging {
  
  def sortBy[K](
      f: (T) => K,//需要指定排序依据:根据什么进行排序;
      ascending: Boolean = true,
      numPartitions: Int = this.partitions.length)
      (implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T] = withScope {
        
    this.keyBy[K](f)
        .sortByKey(ascending, numPartitions)//调用sortByKey实现
        .values
  }
}

5.重分区算子

  1. repartition底层调用coalesce实现重分区能力;
  2. repartition一定会进行shuffle操作;coalesce默认不进行shuffle,但是可以通过设置进行shuffle;
  3. coalesce通过增加shuffle操作避免分区数减少引起的OOM;
  4. repartiton更适合分区数减少的情况;

5.1.coalesce

  1. 支持指定是否进行shuffle;默认不进行shuffle操作;

  2. 无论是否进行shuffle,最后都会返回CoalescedRDD;

  3. 当需要进行shuffle时,会新增一个shuffle操作,然后再返回CoalescedRDD;

  4. 新增一个shuffle操作,保证上游操作任然时分布式并行的,不会因为分区减少导致极端情况;

    • 比如:分区减少为只有一个分区,这将导致所有数据缓存到一个节点上,极有可能导致OOM;

    • 新增shuffle操作,分区减少将在shuffle中完成,上游还是并行进行的;

  5. 在进行shuffle时,会先将上游输出数据均匀的根据shuffle的并行度进行分布;然后使用hash分区器进行分区;

abstract class RDD[T: ClassTag](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]]
  ) extends Serializable with Logging {
  
  def coalesce(numPartitions: Int, shuffle: Boolean = false,//默认不进行shuffle
               partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
              (implicit ord: Ordering[T] = null)
      : RDD[T] = withScope {
    require(numPartitions > 0, s"Number of partitions ($numPartitions) must be positive.")
    if (shuffle) {
      //定义数据在新的分区中的分布
      //从一个随机分区开始,在输出分区中均匀分布元素
      //index:上游RDD的分区索引;items上游index分区的数据迭代器
      val distributePartition = (index: Int, items: Iterator[T]) => {
        //0~numPartitions范围内随机数作为开始位置
        var position = new Random(hashing.byteswap32(index)).nextInt(numPartitions)
        items.map { t =>
          //从position + 1开始均匀分布数据
          position = position + 1
          (position, t)
        }
      } : Iterator[(Int, T)]

      // 新增了一个shuffle操作,使用hash分区器进行分区
      new CoalescedRDD(
        new ShuffledRDD[Int, T, T](
          mapPartitionsWithIndexInternal(distributePartition, isOrderSensitive = true),
          new HashPartitioner(numPartitions)),
        numPartitions,
        partitionCoalescer).values
    } else {
      new CoalescedRDD(this, numPartitions, partitionCoalescer)
    }
  }
}

5.2.repartition

  1. 底层调用coalesce进行能力实现;
  2. 在调用时指定必须进行shuffle操作;
  3. 因为一定会进行shuffle操作,更适合分区数量减少的情况;
abstract class RDD[T: ClassTag](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]]
  ) extends Serializable with Logging {
  
  def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
    //默认必须进行shuffle
    coalesce(numPartitions, shuffle = true)
  }
}

6.集合或者表操作算子

6.1.intersection-RDD交集

实现原理:

  1. 2个rdd都通过map进行转换,rdd中每个元素都转为(v, null)元组;v为rdd中的元素;
  2. 使用cogroup将2个转换后的rdd根据key进行分组,每个key生成一个rdd,生成的rdd中包含一个元组,其中包含该键以及其他键的值列表;
  3. 判断调用RDD和other中key同时存在值列表,同时存在则代表2个RDD都存在该元素;

分区器:

  1. 可以不使用分区器;也可以指定分区器;
  2. 如果参数传递了分区数,则使用hash分区器;分区数为传递的参数;

特别说明:

  1. 2个RDD的元素的数据类型应该一致;
abstract class RDD[T: ClassTag](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]]
  ) extends Serializable with Logging {
  
  def intersection(other: RDD[T]): RDD[T] = withScope {
    //cogroup:对于调用RDD和other中的每个键k,返回一个结果RDD,其中包含一个元组,其中包含该键以及其他键的值列表
    this.map(v => (v, null)).cogroup(other.map(v => (v, null)))
    		//如果调用RDD和other中key同时存在值列表,代表2个RDD都存在该元素
        .filter { case (_, (leftGroup, rightGroup)) => leftGroup.nonEmpty && rightGroup.nonEmpty }
        .keys
  }
  
  //使用指定分区器
  def intersection(
      other: RDD[T],
      partitioner: Partitioner)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
    this.map(v => (v, null)).cogroup(other.map(v => (v, null)), partitioner)
        .filter { case (_, (leftGroup, rightGroup)) => leftGroup.nonEmpty && rightGroup.nonEmpty }
        .keys
  }
  
  //使用hash分区器
  def intersection(other: RDD[T], numPartitions: Int): RDD[T] = withScope {
    intersection(other, new HashPartitioner(numPartitions))
  }
}

6.2.subtractByKey-RDD取差

功能:

  1. 返回一个RDD,其中包含来自this的键不在other中的对;

分区器:

  1. 参数指定分区器,使用指定分区器;
  2. 参数指定分区数,根据分区数构架hash分区器使用;
  3. 参数分区数和分区器都不指定,使用调用RDD的分区器或以其分区数构建一个hash分区器;

实现原理:

  1. 构建SubtractedRDD;

特别说明:

  1. 适用于pairRDD;及k-v类型RDD;
class PairRDDFunctions[K, V](self: RDD[(K, V)])
    (implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null)
  extends Logging with Serializable {
    
  //使用调用RDD的分区器或以其分区数构建一个hash分区器
	def subtractByKey[W: ClassTag](other: RDD[(K, W)]): RDD[(K, V)] = self.withScope {
    subtractByKey(other, self.partitioner.getOrElse(new HashPartitioner(self.partitions.length)))
  }    
   
  //使用hash分区器
  def subtractByKey[W: ClassTag](
      other: RDD[(K, W)],
      numPartitions: Int): RDD[(K, V)] = self.withScope {
    subtractByKey(other, new HashPartitioner(numPartitions))
  }
    
  //使用指定分区器
  def subtractByKey[W: ClassTag](other: RDD[(K, W)], p: Partitioner): RDD[(K, V)] = self.withScope {
    new SubtractedRDD[K, V, W](self, other, p)
  }
}

6.3.subtract-RDD取差

功能:

  1. 返回一个RDD,该RDD包含该RDD中不存在于其他RDD中的元素;

分区器:

  1. 参数指定分区器,使用指定分区器;
    • 调用前后RDD分区器相同,使用调用前RDD的分区器,根据分区器参数重写分区器分区数和分区获取函数;
    • 调用前后RDD分区器不同,使用参数指定分区器;
  2. 参数指定分区数,根据分区数构架hash分区器使用;
  3. 参数分区数和分区器都不指定,使用调用RDD的分区器或以其分区数构建一个hash分区器;

实现原理:

  1. 底层调用subtractByKey实现RDD去差能力;

特别说明:

  1. subtract和subtractByKey的区别在于使用对象不同:subtractByKey适用于pairRDD;subtract适用于单元素RDD;
abstract class RDD[T: ClassTag](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]]
  ) extends Serializable with Logging {
  
  //使用调用RDD的分区器或以其分区数构建一个hash分区器
  def subtract(other: RDD[T]): RDD[T] = withScope {
    subtract(other, partitioner.getOrElse(new HashPartitioner(partitions.length)))
  }
  
  //使用指定参数构建的hash分区器
  def subtract(other: RDD[T], numPartitions: Int): RDD[T] = withScope {
    subtract(other, new HashPartitioner(numPartitions))
  }
  
  def subtract(
      other: RDD[T],
      p: Partitioner)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
    if (partitioner == Some(p)) {//调用前后分区器相同
      //重写分区数和分区获取方法:参数指定分区器的分区数和分区方式有可能发生变化
      val p2 = new Partitioner() {
        override def numPartitions: Int = p.numPartitions
        override def getPartition(k: Any): Int = p.getPartition(k.asInstanceOf[(Any, _)]._1)
      }
      
      this.map(x => (x, null)).subtractByKey(other.map((_, null)), p2).keys
    } else {//调用前后分区器不同,使用参数指定分区器
      this.map(x => (x, null)).subtractByKey(other.map((_, null)), p).keys
    }
  }
}

6.4.join

功能:

  1. 返回一个包含this和other中key匹配的所有元素对的RDD。每一对元素将以(k, (v1, v2))元组的形式返回;

分区器:

  1. 参数指定了分区器,使用指定分区器;
  2. 参数指定了分区数量,以该分区数构建hash分区器使用;
  3. 没有指定分区器和分区数的情况下,使用默认分区数;

实现原理:

  1. cogroup:
    • 将2个rdd根据key进行分组,每个key生成一个rdd,其中包含一个元组(k,(v1,v2)),其中包含该键以及其他键的值列表。
  2. flatMapValues:
    • 获取每个返回RDD的value;
    • 轮询返回RDD的value,以(v1, v2)元组的形式返回一个(v1, v2)集合;
    • 最后会返回一个新的(k, (v1, v2))元组RDD;

特别说明:

  1. 适用于pairRDD(k-v数据类型RDD);
  2. cogroup在融合2个RDD时,只有2个RDD都存在的key,其数据才会融合到最终的rdd中;
class PairRDDFunctions[K, V](self: RDD[(K, V)])
    (implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null)
  extends Logging with Serializable {
    
  //使用指定分区器
  def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))] = self.withScope {
    this.cogroup(other, partitioner).flatMapValues( pair =>//pair:(rdd1的可以的value,rdd2的key的value)
      //pair._2.iterator:要求右边RDD的value在对应key上也有值
      for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, w)
    )
  }
  
  //使用默认分区器
  def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))] = self.withScope {
    join(other, defaultPartitioner(self, other))
  }
    
  //使用参数指定的分区数构建的hash分区器
  def join[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (V, W))] = self.withScope {
    join(other, new HashPartitioner(numPartitions))
  }
}

6.5.leftOuterJoin

功能:

  1. 执行this和other的左外连接

分区器:

  1. 分区器同join大同小异;

原理:

  1. cogroup:
    • 将2个rdd根据key进行分组,每个key生成一个rdd,其中包含一个元组(k,(v1,v2)),其中包含该键以及其他键的值列表。
  2. flatMapValues:
    • 获取每个返回RDD的value;
    • 判断右边RDD在对应key上是否有值,无值,标识为None;
    • 轮询返回RDD的value,以(v1, v2)元组的形式返回一个(v1, v2)集合;
    • 最后会返回一个新的(k, (v1, v2))元组RDD;

特别说明:

  1. 适用于pairRDD(k-v数据类型RDD);
  2. cogroup在融合2个RDD时,以左边RDD为主:
    • 左边RDD存在的key,右边RDD匹配不上时,以None标识;
    • 右边RDD存在的key,左边RDD匹配不上时,不计入最终返回RDD中;
class PairRDDFunctions[K, V](self: RDD[(K, V)])
    (implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null)
  extends Logging with Serializable {

  //使用指定分区器
  def leftOuterJoin[W](
      other: RDD[(K, W)],
      partitioner: Partitioner): RDD[(K, (V, Option[W]))] = self.withScope {
    this.cogroup(other, partitioner).flatMapValues { pair =>
      if (pair._2.isEmpty) {//当右边RDD在对应key上没有value时,在元组中标识为None
        pair._1.iterator.map(v => (v, None))
      } else {//在对应key上左右rdd都有value
        for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, Some(w))
      }
    }
  }  
   
  //使用默认分区器 
  def leftOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (V, Option[W]))] = self.withScope {
    leftOuterJoin(other, defaultPartitioner(self, other))
  }
    
  //使用参数指定的分区数构建的hash分区器
  def leftOuterJoin[W](
      other: RDD[(K, W)],
      numPartitions: Int): RDD[(K, (V, Option[W]))] = self.withScope {
    leftOuterJoin(other, new HashPartitioner(numPartitions))
  }
}

7.参考资料

Spark 常用Shuffle算子汇总

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值