Spark中的combineByKey算子详解

Spark中的combineByKey算子详解

源码解析:

源码有两种方式:

 /**
    * 
    * @param createCombiner
    * @param mergeValue
    * @param mergeCombiners
    * @tparam C
    * @return
    */
  def combineByKey[C](
                       createCombiner: V => C,
                       mergeValue: (C, V) => C,
                       mergeCombiners: (C, C) => C): RDD[(K, C)] = self.withScope {
    combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners)(null)
/**
    * 
    * @param createCombiner
    * @param mergeValue
    * @param mergeCombiners
    * @param partitioner
    * @param mapSideCombine
    * @param serializer
    * @tparam C
    * @return
    */
  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)
  }
 
  }

参数定义:

  1. createCombiner:组合器函数,用于将V类型转换成C类型,输入参数为RDD[K,V]中的V,输出为C
  2. mergeValue:合并值函数,将一个C类型和一个V类型值合并成一个C类型,输入参数为(C,V),输出为C
  3. mergeCombiners:合并组合器函数,用于将两个C类型值合并成一个C类型,输入参数为(C,C),输出为C
  4. numPartitions:结果RDD分区数,默认保持原有的分区数
  5. partitioner:分区函数,默认为HashPartitioner
  6. mapSideCombine:是否需要在Map端进行combine操作,类似于MapReduce中的combine,默认为true

第一个参数,如果分区中元素的key是第一次出现,那么就使用第一个参数应用到这个元素中,如果遇到的元素经过了第一个参数的处理,那么就让第二个参数应用到已存在的元素和这个元素上,第三个参数是将所有分区的计算结果按照key进行汇总分组计算。
scala代码测试:

 def main(args: Array[String]): Unit = {
    Logger.getLogger("org").setLevel(Level.OFF)
    val spark = SparkSession.builder().master("local").getOrCreate()
    val initialScores = List(("A", 88.0), ("A", 95.0),  ("B", 93.0), ("B", 95.0), ("B", 98.0),("A", 91.0))
    val d1 = spark.sparkContext.parallelize(initialScores,2)
    println(d1.getNumPartitions)
    type MVType = (Int, Double) //定义一个元组类型(科目计数器,分数)
    d1.combineByKey(
      score =>{
        println("第一个",score)
        (1, score)
      } ,
      (c1: MVType, newScore) => {
        println("第二个",c1._1 + 1,c1._2,newScore)
        (c1._1 + 1, c1._2 + newScore)
      },
      (c1: MVType, c2: MVType) =>{
        println("第三个",c1._1 ,c1._2 , c2._1,  c2._2)
        (c1._1 + c2._1, c1._2 + c2._2)
      }
    ).map { case (name, (num, score)) => (name, score / num) }.collect.foreach(println)
  }

计算结果:

(第一个,88.0)
(第二个,2,88.0,95.0)
(第一个,93.0)
(第一个,95.0)
(第二个,2,95.0,98.0)
(第一个,91.0)
(第三个,1,93.0,2,193.0)
(第三个,2,183.0,1,91.0)                                                                          
(B,95.33333333333333)
(A,91.33333333333333)

代码详解:
RDD分为两个分区:
第一分区:(“A”, 88.0), (“A”, 95.0), (“B”, 93.0)
第二分区:(“B”, 95.0), (“B”, 98.0),(“A”, 91.0)

计算前两个参数:

首先第一个分区的计算:

  1. 当计算时,元素(“A”, 88.0)在第一个分区中的key是第一次出现,所以被第一个参数应用得到=>(“A”, (1,88.0));
  2. 当计算时,元素(“A”,95.0)在第一个分区中的key是第二次出现,并且被第一个参数应用过,所以使用第二个参数应用(1,88.0)和(1,95.0)得到(‘A’’,(2,88+95));
  3. 当计算时,元素(“B”,93.0)在第一个分区中的key是第一次出现,所以被第一个参数应用得到=>(“B”,(1,93.0));
(第一个,88.0)
(第二个,2,88.0,95.0)
(第一个,93.0)

其次第二个分区的计算:

  1. 当计算时,元素(“B”, 95.0)在第一个分区中的key是第一次出现,所以被第一个参数应用得到=>(“B”, (1,95.0));
  2. 当计算时,元素(“B”,98.0)在第一个分区中的key是第二次出现,并且被第一个参数应用过,所以使用第二个参数应用(1,95.0)和(1,98.0)得到(‘B’’,(2,95+98));
  3. 当计算时,元素(“A”,91.0)在第一个分区中的key是第一次出现,所以被第一个参数应用得到=>(“A”,(1,91.0));
(第一个,95.0)
(第二个,2,95.0,98.0)
(第一个,91.0)

计算第三个参数

A作为key的分析:

第一个分区得到的是(“A”, (2,88+95)),第二个分区得到的是(“A”, (1,91.0)),然后用第三个参数应用到这两个结果中(汇总分组统计),得到(“A”,(2+1,89+95+91));

B作为key的分析:

第一个分区得到的是(“B”, (1,93)),第二个分区得到的是(“B”, (2,95+98)),然后用第三个参数应用到这两个结果中(汇总分组统计),得到(“B”,(1+2,93+95+98));
返回值:

(第三个,1,93.0,2,193.0)
(第三个,2,183.0,1,91.0) 

再最后用map算子计算一个平均值。

(B,95.33333333333333)
(A,91.33333333333333)

你可能感兴趣的:(Spark)