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