Spark中RDD複雜算子 aggregate()、combineByKeyWithClassTag()與aggregateByKey()

1、aggregate()
方法聲明:

def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U = withScope {}

方法聲明中的重點:
1、aggregate返回類型爲傳入的類型參數U
2、第一個參數列表zeroValue爲U類型的值,是聚合的初始值。
3、兩個函數類型的參數,官方註釋如下:

 * @param seqOp an operator used to accumulate results within a partition
 * @param combOp an associative operator used to combine results from different partitions

前兩點不難理解,第三點中我會解釋seqOp ,combOp。

  • seqOp傳入參數(U,T),其中U爲累加結果,T爲RDD中下一行的值,返回一個類型爲U的值。可以說第一次調用seqOp時,U的初始值爲第一個參數列表U類型的zeroValue,下一次調用seqOp時,U的值爲上一個SeqOp的返回結果。需要注意的是seqOp只在Partition內執行,最終返回一個U類型的聚合結果,比如第i個partition的聚合結果我們暫且稱爲result_i。
  • combOp是用來合併seqOp在不同partition內計算的聚合結果result_i,這也是爲什麼它的傳入參數列表爲(:U,:U),返回也爲U類型。

下面是一個例子,將一個Tuple2類型列表中值轉化爲Json。
(此例爲更鮮明體現aggragete返回的類型更加靈活,不受輸入RDD的限制)

val list2 = List(("a",1),("b",2),("c",3),("d",4))
    val rdd6 = sc.parallelize(list2)

    var jsonobject = new JSONObject()

    val a = rdd6.aggregate[JSONObject](jsonobject)(
      (a:JSONObject,b)=>{
            a.put(b._1.toString,b._2.toString)
            a
          }
      ,(c,d)=>{
        val keyset =  d.keySet()
        import collection.JavaConversions._
        for(key<-keyset){
          c.put(key,d.getString(key))
        }
        c
      }
    )
    println(a)

最終結果爲:{“a”:“1”,“b”:“2”,“c”:“3”,“d”:“4”}

2、combineByKey()
combineByKey是PairRDD的算子,pairRdd大部分聚合操作內部都是調用combineByKey()實現的。
先看一下方法的聲明,(有不同參數類型的重載,我們只看參數最少的這一個):

 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))
  }

三個參數的官方解釋:

 *  - `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.
  • createCombiner:相當於對rdd中的value做一步類似map處理,形成改key下累加初始值,處理成累加器的格式C。
  • mergeValue:與aggregate()第一個參數類似,他是將上一個累加器結果和下一行的值做合併處理,將上一個格式爲C累加結果與rdd只能夠的value合併爲一個C類型的新結果。
  • mergeCombiners:同aggregate()的第二個參數,合併不同分區中相同key的累加結果。

明顯createCombiner,mergeValue是在同一個partition中進行,而在同一個partition中每次遇到新的key,createCombiner都會重新創建一個初始值。

計算班級平均成績的代碼:

val rdd4 = rdd.map(x=>(x.split(" ")(0),x.split(" ")(2)))
//rdd4:((classA,98),(classA,90),(classB,88))
rdd4.combineByKeyWithClassTag [Tuple2[Int,Int]](
      (v:String)=>(v.toInt,1)
      ,(a:(Int,Int),v:String)=>(a._1+v.toInt,a._2+1)
      ,(a:(Int,Int),b:(Int,Int))=>(a._1+b._1,b._2+b._2)
    ).map{case (key,value)=>(key,value._1/value._2)}.foreach(x=>println(x._1+" "+x._2))

3、aggregateByKey()
看完上面兩個這個也就不難理解了。
值得注意的是aggregateByKey()內部是調用了combineByKeyWithClassTag()實現的,,這也印證了“pairRdd大部分聚合操作內部都是調用combineByKey()實現的”說法。

aggregateByKey()部分源碼如下:

  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[U]((v: V) => cleanedSeqOp(createZero(), v),
      cleanedSeqOp, combOp, partitioner)
  }

使用樣例:

rdd4.aggregateByKey [Tuple2[Int,Int]]((0,0))(
      (a:(Int,Int),v:String)=>(a._1+v.toInt,a._2+1)
      ,(a:(Int,Int),b:(Int,Int))=>(a._1+b._1,b._2+b._2)
    ).map{case (key,value)=>(key,value._1/value._2)}.foreach(x=>println(x._1+" "+x._2))

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章