Spark Core:Pair RDD

Pair RDD

Pair RDD是针对键值对RDD进行一些特有的操作,提供了并行操作各个键或跨节点重新进行数据分组的操作接口。
常见的Pair RDD方法包括:

函数名作用
reduceByKey合并具有相同键的值
groupByKey对具有相同键的值进行分组,返回value的迭代器
combineByKey使用不同返回类型合并并具有相同键的值
mapValue对pairRDD中的每个值应用一个函数而不改变键
sortByKey返回一个根据键排序的RDD
join对两个RDD进行内连接
cogroup将两个RDD中拥有相同键的数据分组到一起,返回多个value的迭代器

1. 并行度调优问题

每个 RDD 都有固定数目的分区,分区数决定了在 RDD 上执行操作时的并行度。在执行聚合或分组操作时, 可以要求 Spark 使用给定的分区数。 Spark 始终尝试根据集群的大小推断出一个有意义的默认值, 但是有时候你可能要对并行度进行调优来获取更好的性能表现。
除了分组和聚合方法以外,还可以使用repartition() 和coalesce()函数 对RDD进行重新分区;

  1. colesce函数
    源码:
def coalesce(numPartitions: Int, shuffle: Boolean = false,
             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) {
    /** Distributes elements evenly across output partitions, starting from a random partition. */
    val distributePartition = (index: Int, items: Iterator[T]) => {
      var position = new Random(hashing.byteswap32(index)).nextInt(numPartitions)
      items.map { t =>
        // Note that the hash code of the key will just be the key itself. The HashPartitioner
        // will mod it with the number of total partitions.
        position = position + 1
        (position, t)
      }
    } : Iterator[(Int, T)]

    // include a shuffle step so that our upstream tasks are still distributed
    new CoalescedRDD(
      new ShuffledRDD[Int, T, T](
        mapPartitionsWithIndexInternal(distributePartition, isOrderSensitive = true),
        new HashPartitioner(numPartitions)),
      numPartitions,
      partitionCoalescer).values
  } else {
    new CoalescedRDD(this, numPartitions, partitionCoalescer)
  }
}

coalesce方法最后返回一个coalescedRDD(即一个经过简化的RDD),该RDD分区数为numPartitions。第二个参数shuffle默认为false,即这是一个窄依赖,不会发生shuffle,默认简化RDD的分区数。但如果你想要增加分区数目,则第二个参数shuffle=true,这会是一个HashPartitioner产生更多个分区分布到节点上,从而提高并行度。

  1. repatition函数
    源码:
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
  coalesce(numPartitions, shuffle = true)
}

可以看出,该方法内部调用colesce方法其中shuffle=ture,会产生多于之前分区数目的RDD(也可以减少,但推荐使用colesce方法);该方法返回一个恰好有numParititions个分区的RDD,内部通过shuffle重新分布数据 。

2.combineByKey(聚合操作)

combineByKey() 是最为常用的基于键进行聚合的函数,combineByKey() 可以让用户返回与输入数据的类型不同的
返回值
.combineByKey() 会遍历分区中的所有元素,因此某个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。

源码:

/**
 * :: Experimental ::
 * Generic function to combine the elements for each key using a custom set of aggregation
 * functions. Turns an RDD[(K, V)] into a result of type RDD[(K, C)], for a "combined type" C
 *
 * Users provide three functions:
 *
 *  - `createCombiner`, which turns a V into a C (e.g., creates a one-element list)
 *  - `mergeValue`, to merge a V into a C (e.g., adds it to the end of a list)
 *  - `mergeCombiners`, to combine two C's into a single one.
 *
 * In addition, users can control the partitioning of the output RDD, and whether to perform
 * map-side aggregation (if a mapper can produce multiple items with the same key).
 *
 * @note V and C can be different -- for example, one might group an RDD of type
 * (Int, Int) into an RDD of type (Int, Seq[Int]).
 */
def combineByKey[C](
    createCombiner: V => C,
    mergeValue: (C, V) => C,
    mergeCombiners: (C, C) => C): RDD[(K, C)] = self.withScope {
  combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners)(null)
}

combineByKey函数中必须包含三个元素:

  1. createCombiner函数:combineByKey会遍历每个分区中所有元素,如果在一个分区中遍历到一个未遇到过的Key,通过createCombiner()函数创建这个Key对应的累加器初始值(“combined type” C)注意:这一过程只在每个分区中第一次出现这个Key时才发生,而不是整个Rdd中。
  2. mergeValue函数:当处理遍历到当前分区已经遇到过的Key时,使用mergeValue方法对累加器(C)对应值与新值合并
  3. mergeCombiners函数:每个分区都是独立处理的,对于同一个键可以有多个累加器。如果多个分区都有同一个键的累加器,则通过mergeCombiners方法将各分区结果进行合并。

案例:
计算每个键对应的平均数

val conf = new SparkConf().setMaster("local[*]").setAppName("PairRdd")
val sc = new SparkContext(conf)
val pairRdd = sc.parallelize( List(("coffee",1),("coffee",2),("panda",3),("coffee",9)),2)

val result = pairRdd.combineByKey(
  v => (v, 1),
  (acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1),
  (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2),
).map { case (key, value) => (key, value._1 / value._2.toFloat) }
val r = result.collect
println(r.toBuffer)
sc.stop()

步骤:

  1. 并行化创建一个RDD,设置分区为2
  2. combineByKey进行聚合:第一个函数,将遍历的key对应的Value值作为参数,返回(V,C)作为累加器,C记录遍历到Key值个数;第二个函数:将遍历到相同的Key值的Vaule累加,并计数器C值加一;第三个函数:将不同分区的结果合并
  3. 利用map函数,将每个结果取出来计算平均值

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值