参数:(createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C)
1.作用:对相同K,把V合并成一个集合。
2.参数描述:
combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个键对应的累加器的初始值
如果这是一个在处理当前分区之前已经遇到的键,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并
由于每个分区都是独立处理的, 因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器, 就需要使用用户提供的 mergeCombiners() 方法将各个分区的结果进行合并。
3.需求:创建一个pairRDD,根据key计算每种key的均值。(先计算每个key出现的次数以及可以对应值的总和,再相除得到结果)
4.需求分析:
(1)创建一个pairRDD
scala> val input =
sc.parallelize(Array(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98)),2)
input: org.apache.spark.rdd.RDD[(String, Int)] =
ParallelCollectionRDD[52] at parallelize at <console>:26
(2)将相同key对应的值相加,同时记录该key出现的次数,放入一个二元组
scala> val combine = input.combineByKey((_,1),(acc:(Int,Int),v)=>(acc._1+v,acc._2+1),(acc1:(Int,Int),acc2:(Int,Int))=>(acc1._1+acc2._1,acc1._2+acc2._2))
combine: org.apache.spark.rdd.RDD[(String, (Int, Int))] = ShuffledRDD[5] at combineByKey at <console>:28
(3)打印合并后的结果
scala> combine.collect
res5: Array[(String, (Int, Int))] = Array((b,(286,3)), (a,(274,3)))
(4)计算平均值
scala> val result = combine.map{case (key,value) => (key,value._1/value._2.toDouble)}
result: org.apache.spark.rdd.RDD[(String, Double)] = MapPartitionsRDD[54] at map at <console>:30
(5)打印结果
scala> result.collect()
res33: Array[(String, Double)] = Array((b,95.33333333333333), (a,91.33333333333333))
1.首先是分区
我理解的,对这样的Array数组分区 就是根据个数来切分 分区的
比如上面我有两个分区 那么我就中间切开 将两边的数据放进两个分区,
对不对不知道、、、
通过glom算子查看分区结果:
定义2个分区
val input = sc.parallelize(Array(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98)),2)
-------------------------------------------------------------
分区结果
scala> input.glom.collect
res1: Array[Array[(String, Int)]] = Array(Array((a,88), (b,95), (a,91)), Array((b,93), (a,95), (b,98)))
-----------------------------------------------------
即 两个分区
Array((a,88), (b,95), (a,91))
Array((b,93), (a,95), (b,98))
-----------------------------------------
2.三个操作 (createCombiner,mergeValue,mergeCombiners)
前两个操作是分区内的,第三个操作是分区间
因为这个算子的名字combineByKey,是ByKey 通过key来分组操作的
对第一个操作的理解,createCombiner, 其实就是 初始化
(可以先看看上一篇博客的scala中的 Scala中的fold和reduce理解)
那到底怎么初始化呢
首先 我们已经分区了 每个分区都是相同的操作,
所以第一个分区为例: Array((a,88), (b,95), (a,91))
ByKey嘛,所以 有两个key a, b
a : 88, 91
b : 95
这第一个操作createCombiner:
记住昂
所以操作是这个 (_,1) 结果 就是
a : (88, 1) , 91
b : (95, 1)
注意 对于相同的key只挑选一个(一般是第一个)做初始化操作
这个操作的结果 就叫 acc
所以下一个操作的第一个参数 acc 的类型是 acc : (Int, Int)
第二个参数v 就是 相同key的其他值 比如 a 的 91
所以我们第二个操作mergeValue的逻辑根据题目要求可以是:
mergeValue
(acc:(Int,Int),v)=>(acc._1+v,acc._2+1)
对于相同key 值相加起来,此处每次操作加1 以记录次数
两个分区出来的结果是:
第一个分区:
a : ( 179, 2) ,
b : (95, 1)
第二个分区
a : (95,1)
b : (191, 2)
第三个操作mergeCombiners就是分区间的操作
(acc1:(Int,Int),acc2:(Int,Int))=>(acc1._1+acc2._1,acc1._2+acc2._2))
根据key将第一二个参数相加
结果的类型 [(String, (Int, Int))]
所以是 (a,(274,3)),(b,(286,3))
OK 很简单
感觉需要注意的:
1.就是第一 二个操作是分区内操作,第三个是分区间操作,
且第一个操作为初始化操作,
且在分区下相同的key只执行一次初始化操作,一般是挑选key的第一个V值作为操作对象
同理,
因为 aggregateByKey 底层调用的也是 combineByKey 所以两者是差不多的,
参数:(zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)
(1)创建一个pairRDD
scala> val rdd = sc.parallelize(List(("a",3),("a",2),("c",4),("b",3),("c",6),("c",8)),2)
rdd: org.apache.spark.rdd.RDD[(String, Int)] =
ParallelCollectionRDD[0] at parallelize at <console>:24
(2)取出每个分区相同key对应值的最大值,然后相加
scala> val agg = rdd.aggregateByKey(0)(math.max(_,_),_+_)
agg: org.apache.spark.rdd.RDD[(String, Int)] =
ShuffledRDD[1] at aggregateByKey at <console>:26
(3)打印结果
scala> agg.collect()
res0: Array[(String, Int)] = Array((b,3), (a,3), (c,12))