spark RDD,reduceByKey vs groupByKey
Spark中有两个类似的api,分别是reduceByKey和groupByKey。这两个的功能类似,但底层实现却有些不同,那么为什么要这样设计呢?我们来从源码的角度分析一下。
先看两者的调用顺序(都是使用默认的Partitioner,即defaultPartitioner)
所用spark版本:spark2.1.0
先看reduceByKey
Step1
def reduceByKey(func: (V, V) => V): RDD[(K, V)] = self.withScope {
reduceByKey(defaultPartitioner(self), func)
}
Setp2
def reduceByKey(partitioner: Partitioner, func: (V, V) => V): RDD[(K, V)] = self.withScope {
combineByKeyWithClassTag[V]((v: V) => v, func, func, partitioner)
}
Setp3
def combineByKeyWithClassTag[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C,
partitioner: Partitioner,
mapSideCombine: Boolean = true,
serializer: Serializer = null)(implicit ct: ClassTag[C]): RDD[(K, C)] = self.withScope {
require(mergeCombiners != null, "mergeCombiners must be defined") // required as of Spark 0.9.0
if (keyClass.isArray) {
if (mapSideCombine) {
throw new SparkException("Cannot use map-side combining with array keys.")
}
if (partitioner.isInstanceOf[HashPartitioner]) {
throw new SparkException("HashPartitioner cannot partition array keys.")
}
}
val aggregator = new Aggregator[K, V, C](
self.context.clean(createCombiner),
self.context.clean(mergeValue),
self.context.clean(mergeCombiners))
if (self.partitioner == Some(partitioner)) {
self.mapPartitions(iter => {
val context = TaskContext.get()
new InterruptibleIterator(context, aggregator.combineValuesByKey(iter, context))
}, preservesPartitioning = true)
} else {
new ShuffledRDD[K, V, C](self, partitioner)
.setSerializer(serializer)
.setAggregator(aggregator)
.setMapSideCombine(mapSideCombine)
}
}
姑且不去看方法里面的细节,我们会只要知道最后调用的是combineByKeyWithClassTag这个方法。这个方法有两个参数我们来重点看一下,
def combineByKeyWithClassTag[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C,
partitioner: Partitioner,
mapSideCombine: Boolean = true,
serializer: Serializer = null)
首先是partitioner参数,这个即是RDD的分区设置。除了默认的defaultPartitioner,Spark还提供了RangePartitioner和HashPartitioner外,此外用户也可以自定义partitioner。通过源码可以发现如果是HashPartitioner的话,那么是会抛出一个错误的。
然后是mapSideCombine参数,这个参数正是reduceByKey和groupByKey最大不同的地方,它决定是是否会先在节点上进行一次Combine操作,下面会有更具体的例子来介绍。
然后是groupByKey
Step1
def groupByKey(): RDD[(K, Iterable[V])] = self.withScope {
groupByKey(defaultPartitioner(self))
}
Step2
def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])] = self.withScope {
// groupByKey shouldn't use map side combine because map side combine does not
// reduce the amount of data shuffled and requires all map side data be inserted
// into a hash table, leading to more objects in the old gen.
val createCombiner = (v: V) => CompactBuffer(v)
val mergeValue = (buf: CompactBuffer[V], v: V) => buf += v
val mergeCombiners = (c1: CompactBuffer[V], c2: CompactBuffer[V]) => c1 ++= c2
val bufs = combineByKeyWithClassTag[CompactBuffer[V]](
createCombiner, mergeValue, mergeCombiners, partitioner, mapSideCombine = false)
bufs.asInstanceOf[RDD[(K, Iterable[V])]]
}
Setp3
def combineByKeyWithClassTag[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C,
partitioner: Partitioner,
mapSideCombine: Boolean = true,
serializer: Serializer = null)(implicit ct: ClassTag[C]): RDD[(K, C)] = self.withScope {
require(mergeCombiners != null, "mergeCombiners must be defined") // required as of Spark 0.9.0
if (keyClass.isArray) {
if (mapSideCombine) {
throw new SparkException("Cannot use map-side combining with array keys.")
}
if (partitioner.isInstanceOf[HashPartitioner]) {
throw new SparkException("HashPartitioner cannot partition array keys.")
}
}
val aggregator = new Aggregator[K, V, C](
self.context.clean(createCombiner),
self.context.clean(mergeValue),
self.context.clean(mergeCombiners))
if (self.partitioner == Some(partitioner)) {
self.mapPartitions(iter => {
val context = TaskContext.get()
new InterruptibleIterator(context, aggregator.combineValuesByKey(iter, context))
}, preservesPartitioning = true)
} else {
new ShuffledRDD[K, V, C](self, partitioner)
.setSerializer(serializer)
.setAggregator(aggregator)
.setMapSideCombine(mapSideCombine)
}
}
结合上面reduceByKey的调用链,可以发现最终其实都是调用combineByKeyWithClassTag这个方法的,但调用的参数不同。
reduceByKey的调用
combineByKeyWithClassTag[V]((v: V) => v, func, func, partitioner)
groupByKey的调用
combineByKeyWithClassTag[CompactBuffer[V]](
createCombiner, mergeValue, mergeCombiners, partitioner, mapSideCombine = false)
正是两者不同的调用方式导致了两个方法的差别,我们分别来看
reduceByKey的泛型参数直接是[V],而groupByKey的泛型参数是[CompactBuffer[V]]。这直接导致了reduceByKey和groupByKey的返回值不同,前者是RDD[(K, V)],而后者是RDD[(K, Iterable[V])]
然后就是mapSideCombine=false了,这个mapSideCombine参数的默认是true的。这个值有什么用呢,上面也说了,这个参数的作用是控制要不要在map端进行初步合并(Combine)。可以看看下面具体的例子。


从功能上来说,可以发现ReduceByKey其实就是会在每个节点先进行一次合并的操作,而groupByKey没有。
这么来看ReduceByKey的性能会比groupByKey好很多,因为有些工作在节点已经处理了。那么groupByKey为什么存在,它的应用场景是什么呢?我也不清楚,如果观看这篇文章的读者知道的话不妨在评论里说出来吧。非常感谢!
spark RDD,reduceByKey vs groupByKey的更多相关文章
- spark RDD,DataFrame,DataSet 介绍
弹性分布式数据集(Resilient Distributed Dataset,RDD) RDD是Spark一开始就提供的主要API,从根本上来说,一个RDD就是你的数据的一个不可变的分布式元素集合,在 ...
- Spark SQL,如何将 DataFrame 转为 json 格式
今天主要介绍一下如何将 Spark dataframe 的数据转成 json 数据.用到的是 scala 提供的 json 处理的 api. 用过 Spark SQL 应该知道,Spark dataf ...
- 【Spark算子】:reduceByKey、groupByKey和combineByKey
在spark中,reduceByKey.groupByKey和combineByKey这三种算子用的较多,结合使用过程中的体会简单总结: 我的代码实践:https://github.com/wwcom ...
- reduceByKey和groupByKey区别与用法
在spark中,我们知道一切的操作都是基于RDD的.在使用中,RDD有一种非常特殊也是非常实用的format——pair RDD,即RDD的每一行是(key, value)的格式.这种格式很像Pyth ...
- Spark RDD/Core 编程 API入门系列 之rdd案例(map、filter、flatMap、groupByKey、reduceByKey、join、cogroupy等)(四)
声明: 大数据中,最重要的算子操作是:join !!! 典型的transformation和action val nums = sc.parallelize(1 to 10) //根据集合创建RDD ...
- Spark RDD/Core 编程 API入门系列之map、filter、textFile、cache、对Job输出结果进行升和降序、union、groupByKey、join、reduce、lookup(一)
1.以本地模式实战map和filter 2.以集群模式实战textFile和cache 3.对Job输出结果进行升和降序 4.union 5.groupByKey 6.join 7.reduce 8. ...
- 【spark】常用转换操作:reduceByKey和groupByKey
1.reduceByKey(func) 功能: 使用 func 函数合并具有相同键的值. 示例: val list = List("hadoop","spark" ...
- Spark 学习笔记之 distinct/groupByKey/reduceByKey
distinct/groupByKey/reduceByKey: distinct: import org.apache.spark.SparkContext import org.apache.sp ...
- spark中的pair rdd,看这一篇就够了
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是spark专题的第四篇文章,我们一起来看下Pair RDD. 定义 在之前的文章当中,我们已经熟悉了RDD的相关概念,也了解了RDD基 ...
随机推荐
- 更改mysql 数据目录
1.停止MySQL服务 service mysqld stop 2.移动数据到新位置 mv /var/lib/mysql /newdir/data/ 3.修改/etc/my.cnf datadir=/ ...
- Android布局:宽度适应的横向跟随,防止挤掉重要视图
不知道这样的布局该怎么描述,标题也是乱取的..直接上图吧 最近遇到了这样要求的布局: 1.上图中的“标题”长度不定,“状态”标签可能有多个并紧跟在标题右边,“属性”一直居右显示: 2.当“标题”过长, ...
- IntelliJ IDEA 代码字体大小的快捷键设置放大缩小(很实用)(图文详解)
不多说,直接上干货! 这是在设置IntelliJ IDEA 代码字体的快捷键设置缩小: 怎么达到的了,就是ctrl + 你的鼠标滑扭往下 这是在设置IntelliJ IDEA 代码字体的快捷键设置 ...
- Java 容器源码分析之 ArrayList
概览 ArrayList是最常使用的集合类之一了.在JDK文档中对ArrayList的描述是:ArrayList是对list接口的一种基于可变数组的实现.ArrayList类的声明如下: 12 pub ...
- 机器学习排序算法:RankNet to LambdaRank to LambdaMART
使用机器学习排序算法LambdaMART有一段时间了,但一直没有真正弄清楚算法中的所有细节. 学习过程中细读了两篇不错的博文,推荐给大家: 梯度提升树(GBDT)原理小结 徐博From RankNet ...
- Nacos系列:Nacos的Java SDK使用
Maven依赖 Nacos提供完整的Java SDK,便于配置管理和服务发现及管理,以 Nacos-0.8.0 版本为例 添加Maven依赖: <dependency> <group ...
- ZooKeeper系列(6):ZooKeeper的伸缩性和Observer角色
ZooKeeper系列文章:https://www.cnblogs.com/f-ck-need-u/p/7576137.html#zk 1.ZooKeeper中的角色 在比较老的ZooKeeper版本 ...
- Ubuntu16---安装mysql5.7未提示输入密码,安装后修改mysql密码默认密码
Ubuntu16安装mysql5.7未提示输入密码,安装后修改mysql密码默认密码 mysql默认密码为空 但是使用mysql -uroot -p 命令连接mysql时,报错 ERROR 1045 ...
- Spring Cloud Finchley版中Consul多实例注册的问题处理
由于Spring Cloud对Etcd的支持一直没能从孵化器中出来,所以目前来说大多用户还在使用Eureka和Consul,之前又因为Eureka 2.0不在开源的消息,外加一些博眼球的标题党媒体使得 ...
- 跨域 webpack + vue-cil 中 proxyTable 处理跨域
博客地址:https://ainyi.com/27 跨域 了解同源政策:所谓"同源"指的是"三个相同". 协议相同 域名相同 端口相同 解决跨域 jsonp 缺 ...