spark中的aggregateByKey算子详解:
源码解析:
源码有三种格式:
/**
* 自定义分区器Partitioner
* @param zeroValue 初始值(默认值)
* @param partitioner 自定义分区器
* @param seqOp
* @param combOp
* @tparam U
* @return
*/
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)
}
/**
* 默认使用hash分区器 HashPartitioner
* @param zeroValue 初始值(默认值)
* @param numPartitions 分区个数
* @param seqOp
* @param combOp
* @tparam U
* @return
*/
def aggregateByKey[U: ClassTag](zeroValue: U, numPartitions: Int)(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)] = self.withScope {
aggregateByKey(zeroValue, new HashPartitioner(numPartitions))(seqOp, combOp)
}
/**
* 默认使用RDD的分区
* @param zeroValue 初始值
* @param seqOp
* @param combOp
* @tparam U
* @return
*/
def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)] = self.withScope {
aggregateByKey(zeroValue, defaultPartitioner(self))(seqOp, combOp)
}
参数定义:
以上述第三个aggregateByKey为例, 三个参数的含义分别为:
第一个参数是, 每个key的初始值 ,比如为0,nil,null;
第二个是个函数, Seq Function, 经测试这个函数就是用来先对 每个分区 内的数据 按照key分别进行 函数定义的操作;
第三个是个函数, Combiner Function, 对经过 Seq Function 处理过的数据 按照key分别进行 函数定义的操作。
scala代码测试:
def main(args: Array[String]): Unit = {
Logger.getLogger("org").setLevel(Level.OFF)
val spark = SparkSession.builder().master("local").getOrCreate()
val list = List((1,2),(2,7),(1,3),(2,8),(3,9),(3,10)
,(1,4),(1,5),(2,6),(2,11),(3,12),(3,13))
val listRDD = spark.sparkContext.parallelize(list,2)
println(listRDD.getNumPartitions)
listRDD.aggregateByKey(0)((a,b)=>{
println("第一个:"+a+","+b)
math.max(a,b)
},(x,y)=>{
println("第二个:"+x+","+y)
x+y
}).collect().foreach(println)
}
代码详解:
listRDD分为两个分区:
第一个分区:(1,2),(2,7),(1,3),(2,8),(3,9),(3,10)
第二个分区:(1,4),(1,5),(2,6),(2,11),(3,12),(3,13)
打印结果:
第一个:0,2
第一个:0,7
第一个:2,3
第一个:7,8
第一个:0,9
第一个:9,10
第一个:0,4
第一个:4,5
第一个:0,6
第一个:6,11
第一个:0,12
第一个:12,13
第二个:3,5
第二个:8,11
第二个:10,13
(1,8)
(2,19)
(3,23)
首先第一个参数计算:
- 遍历第一个值为(1,2)的时候,a是key为1的累加器,会把初始值zeroValue赋给这个累加器,b代表键值对(1,2)中的值2,之后将返回值2赋回给这个累加器;
- 遍历第二个值为(2,7)的时候,a是key为2的累加器,会把初始值zeroValue赋给这个累加器,b代表键值对(2,7)中的值7,之后将返回值7赋回给这个累加器;
- 遍历第二个值为(1,3)的时候,a是key为1的累加器,由于之前第一步骤中已经有key为1的累加器,并且值为2,b代表键值对(1,3)中的值3,之后将返回值3赋回给这个累加器;
- 遍历第二个值为(2,8)的时候,a是key为2的累加器,由于之前第2步骤中已经有key为2的累加器,并且值为7,b代表键值对(2,8)中的值8,之后将返回值8赋回给这个累加器;
- 以此类推,计算出第一分区的数据;
- 同理计算出第二分区的数据;
第一个参数最后的返回值为:
第一分区:
(1,3)
(2,8)
(3,10)
第二分区:
(1,5)
(2,11)
(3,13)
其次第二个参数计算:
第二个参数是计算所有分区相同key的数据集(x,y):
key=1时,(3,5)
key=2时,(8,11)
key=3时:(10,13)
最后计算结果:
(1,8)
(2,19)
(3,23)