1

RDD的CombineByKey使用方法

这是一个很抽象化的方法,一开始看得一头雾水。但是大部分的聚合函数都基于这个方法去实现的,比如常用的reduceByKey,所以这个方法很重要。

方法参数

def combineByKey[C](
      //在找到给定分区中第一次碰到的key(在RDD元素中)时被调用。此方法为这个key初始化一个累加器。
      createCombiner: V => C,
      //当累加器已经存在的时候(也就是上面那个key的累加器)调用。
      mergeValue: (C, V) => C,
      // 如果哪个key跨多个分区,该参数就会被调用。
      mergeCombiners: (C, C) => C,
      partitioner: Partitioner,
      mapSideCombine: Boolean = true,
      serializer: Serializer = null
): RDD[(K, C)] = { //实现略 }

原理

由于combineByKey()会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。

  • 如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个键对应的累加器的初始值。需要注意的是,这一过程会在每个分区中第一次出现各个键时发生,而不是在整个RDD中第一次出现一个键时发生。
  • 如果这是一个在处理当前分区之前已经遇到的键,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并。
  • 由于每个分区都是独立处理的,因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器,就需要使用用户提供的mergeCombiners()方法将各个分区的结果进行合并。

举例

根据原理,粗略讲一下大概的流程

  1. 第一个方法参数createCombiner,会去遍历某个分区第一个出现的key所对应的value,然后赋值成元祖格式,这里牢记是第一个。例:(50,1)
  2. 假设(maths,50)和(maths,60)在一个分区里面,那么会调用第二个方法参数mergeValue。 也就是(maths,50)-->(maths,(50,1)),而(maths,60)不变,然后调用第二个方法参数-->(50+60,1+1)
  3. 假设(maths,50)和(maths,60)不在同一个分区里面,那么会调用第三个方法参数mergeCombiners,也就是maths,50)-->(maths,(50,1)),(maths,60)-->(maths,(60,1)),接着调用第三个参数-->(50+60,1+1)

可以手动调整分区数,来看它不同的表现。

test("combine by key "){
    val inputrdd = spark.sparkContext.parallelize(Seq(
      ("maths", 50), ("maths", 60),
      ("english", 65),
      ("physics", 66), ("physics", 61), ("physics", 87)),
      3)

    val reduced = inputrdd.combineByKey(
      (mark) => {
        println(s"Create combiner -> ${mark}")
        (mark, 1)
      },
      (acc: (Int, Int), v) => {
        println(s"""Merge value : (${acc._1} + ${v}, ${acc._2} + 1)""")
        (acc._1 + v, acc._2 + 1)
      },
      (acc1: (Int, Int), acc2: (Int, Int)) => {
        println(s"""Merge Combiner : (${acc1._1} + ${acc2._1}, ${acc1._2} + ${acc2._2})""")
        (acc1._1 + acc2._1, acc1._2 + acc2._2)
      }
    )
    reduced.collect().foreach(println)
  }

小鸡
214 声望24 粉丝

1.01的365次方=37.8