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()方法將各個分區的結果進行合併。
舉例
根據原理,粗略講一下大概的流程
- 第一個方法參數createCombiner,會去遍歷某個分區第一個出現的key所對應的value,然後賦值成元祖格式,這裏牢記是第一個。例:(50,1)
- 假設(maths,50)和(maths,60)在一個分區裏面,那麼會調用第二個方法參數mergeValue。 也就是(maths,50)-->(maths,(50,1)),而(maths,60)不變,然後調用第二個方法參數-->(50+60,1+1)
- 假設(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)
}