easy-algorithm-interview-an.../bigdata/spark/combineByKey实例详解.md

3.6 KiB
Raw Blame History

我们在做数据统计与分析的时候经常会遇到K-V结构的数据所以处理这种K-V结构的数据也是非常常见的需求。在Spark中除了原生的RDD天然有这种K,V结构API中也包含有javaPairRdd,PairwiseRdd等对应的接口。而对于KV结构的数据处理就有很多种情况了例如像数据库的group by操作等。今天我们就来说说在spark中一个常用的操作combineByKey

1.combineByKey函数原型

  /**
   * Simplified version of combineByKeyWithClassTag that hash-partitions the resulting RDD using the
   * existing partitioner/parallelism level. This method is here for backward compatibility. It
   * does not provide combiner classtag information to the shuffle.
   *
   * @see [[combineByKeyWithClassTag]]
   */
  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是典型的K-V类型的算子而且是一个transformation操作。与其他transformation操作一样combineByKey也不会触发作业的提交。combineByKey函数主要有三个参数而且这三个参数都是函数

createCombiner: V => C 产生一个combiner的函数将RDD[K,V]中的V转换成一个新的值C1
mergeValue: (C, V) => C 合并value的函数将一个C1类型的值与一个V合并成一个新的C类型的值假设这个新的C类型的值为C2
mergeCombiners: (C, C) => C) 将两个C类型的值合并为一个C类型的值

整个函数最后的输出为RDD[(K, C)]

2.看个实际例子

假设hdfs上有个文本文本有两列第一列为city城市名第二列为用户标识uuid现在想统计每个城市有多少UV并排序用combineByKey就可以实现上述需求。源码如下

    def t1(sc: SparkContext) = {
        val inputpath = "XXX"
        sc.textFile(inputpath).
            filter { x =>
                val lines = x.split("\t")
                lines.length == 2 && lines(1).length > 0
            }
            .map { x =>
                val lines = x.split("\t")
                val (city, uuid) = (lines(0), lines(1))
                (city, uuid)
            }
            .combineByKey((v: String) => {
                val set = new java.util.HashSet[String]()
                set.add(v)
                set
            },
                (x: java.util.HashSet[String], v: String) => {
                    x.add(v)
                    x
                },
                (x: java.util.HashSet[String], y: java.util.HashSet[String]) => {
                    x.addAll(y)
                    x
                })
            .map(x => (x._1, x._2.size()))
            .sortBy(_._2, false)
            .take(10)
            .foreach(println)
    }

代码详解:

(v: String) => {
                val set = new java.util.HashSet[String]()
                set.add(v)
                set

这个函数表示对于每一个city第一次出现的时候先new一个hashset并把此时的uuid加入到hashset中。

(x: java.util.HashSet[String], v: String) => {
                    x.add(v)
                    x
                }

这个表示将每一个uuid都merge到已有的combiner中。

 (x: java.util.HashSet[String], y: java.util.HashSet[String]) => {
                    x.addAll(y)
                    x
                }

最后一个函数表示将所有city对应的uuid的hashset合并得到的就是每个city的所有uuid集合达到了我们最终的目的