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基 ...
随机推荐
- android项目架构 -----Android 知识体系与常用第三方框架
好东西值得分享 ,这是网络上总结的一些开源的东西直接就拿过来了 .... Android通用流行框架大全 先把这张图放在这 ,先来谈一谈项目结构 .我喜欢将东西按模块来划分: 都知道module . ...
- SpringCloud实战10-Sleuth
Spring-Cloud-Sleuth是Spring Cloud的组成部分之一,为SpringCloud应用实现了一种分布式追踪解决方案,其兼容了Zipkin, HTrace和log-based追踪, ...
- linux 命令 — xargs
xargs xargs能接收stdin并将其转化为特定命令的命令行参数,构建单行命令的重要工具 command | xargs 指定分隔符 echo "splitXsplitXsplitXs ...
- 纯JS实现加载更多(VUE框架)
<template> <div class = 'car_list' reft='scrollobx' @scroll='scrollready($event)'> </ ...
- 【SqlServer系列】数据库三大范式
1 概述 一般地,在进行数据库设计时,应遵循三大原则,也就是我们通常说的三大范式,即第一范式要求确保表中每列的原子性,也就是不可拆分:第二范式要求确保表中每列与主键相关,而不能只与主键的某部分相关 ...
- python实战学习之numpy学习
numpy基础要点 1.生成数组 np.array([]) 2.变量的类型 numpy.ndarray 3.数据的类型 int8,float64,float32,bool等 4.数据的类型转换 x.a ...
- OJ:一道考察多态的题目
Description 下面程序的输出结果是: A::Fun C::Do 程序代码 #include <iostream> using namespace std; class A { p ...
- 南大算法设计与分析课程OJ答案代码(4)--变位词、三数之和
问题 A: 变位词 时间限制: 2 Sec 内存限制: 10 MB提交: 322 解决: 59提交 状态 算法问答 题目描述 请大家在做oj题之前,仔细阅读关于抄袭的说明http://www.bi ...
- golang包管理解决之道——go modules初探
golang的包管理是一直是为人诟病之处,从golang1.5引入的vendor机制,到准官方工具dep,目前为止还没一个简便的解决方案. 不过现在go modules随着golang1.11的发布而 ...
- Spring Boot入门(11)实现文件下载功能
在这篇博客中,我们将展示如何在Spring Boot中实现文件的下载功能. 还是遵循笔者写博客的一贯风格,简单又不失详细,实用又能让你学会. 本次建立的Spring Boot项目的主要功能 ...