2.1.4 By
2.1.4.1、combineByKey
功能:使用一组自定义聚合函数来组合每个键元素的通用函数。
返回:将RDD[K,V]转换成RDD[K,C]
操作:底层其实也是调用的combineByKeyWithClassTag
def combineByKey[C](
// 类似于一个初始化操作来创建key对应的累积器的初始值。
// (每个分区会有多个,这跟key是不是第一次在分区内出现有关)
createCombiner: V => C,
// 合并同一个分区内的值
mergeValue: (C, V) => C,
// 合并多分区的值
mergeCombiners: (C, C) => C): RDD[(K, C)] = self.withScope {
combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners)(null)
}
2.1.4.2、groupByKey
功能:将RDD中的每个Key进行分组到一个序列集合中
返回:RDD[(K, Iterable[V])]
操作:其实就是按照一定分区规则进行分组,将相同的key的value组装到一个序列中去,然后不保证组内有序的。这个操作比较耗资源,如果一个key对应的value非常多,那么很容易出现OOM。所以一般使用reduceByKey或者aggregateByKey来替代。
2.1.4.3、reduceByKey(Lazy)
功能:使用关联和可交换的归约函数合并每个键的值;分区内聚合和分区见聚合的逻辑一样。
返回:RDD[(K, V)]
操作:这个算子在将结果发送到reduce端之前会先在本地进行合并,类似于MR中的Combiner功能。底层会调用combineByKeyWithClassTag
2.1.4.3.1、reduceByKeyLocally
区别:和reduceByKey的区别在于返回的结果值不是一个RDD[K,V],而是一个Map[K,V]
2.1.4.4、aggregateByKey
功能:使用给定的组合函数和默认/中性值(比如0)聚合每个键的值
返回:返回结果的类型可能会和值的类型不一样。即可以返回和这个RDD V中的值类型不同的结果类型U
操作:首先会合并分区内的值(即mergeValue),然后再合并分区之间的值(mergeCombiners)。其实底层会调用combineByKeyWithClassTag。
在内部调用了一个cleanedSeqOp(createZero(), v)函数,其实这个函数的功能跟combineByKey中的createCombiner功能一样。该函数同时也是mergeValue。也就是说当createCombiner和mergeValue函数相同的时候,使用aggregateByKey代替combineByKey比较合适。
2.1.4.5、foldByKey
功能:使用关联函数和中性的“零值”合并每个键的值,可以将其添加到结果中任意次数,并且不得更改结果;相对于aggregateByKey来说局部聚合和全局聚合的规则是一样的,而aggregateByKey对应的局部聚合和全局聚合的规则可以不一样。和reduceByKe相比,多了一个0值处理的功能。
返回:用于RDD[K,V]根据K将V做折叠、合并处理。
操作:其中第一个参数zeroValue会先应用到Value值中做初始化操作,然后再将func应用于初始化之后的value值上。
2.1.4.6、VS
2.1.5、SparkContext
SparkContext可以说是spark框架中最为重要的一部分。为什么这么说呢?我们以spark应用的生命周期为例子来阐述一下。
1、应用提交时期
当我们把应用程序打包部署提交到集群时,那么首先要做的事情就是要实例化SparkContext,并初始化应用程序运行时所用到的一些组件,比如说DAGScheduler、TaskScheduler、SChedulerBackend。同时程序启动时还会向Master注册,那么这个工作也是由SparkContext完成的。
2、应用计算时期
当程序提交之后,获取到资源就开始真正的计算了,那么在计算的过程中需要分发Task,task在执行的过程中需要监控其运行状态,以及中间的资源分配释放等动作。那么这一切的操作其都是在SparkContext为核心的调度指挥下进行的。
3、应用结束时期
当所有的task执行完成之后,也就意味着应用程序的结束,那么这个时候sparkContext就是关闭代表着资源的释放。
因此SparkContext贯穿着应用程序的整个生命周期,可想而知其重要程度。