spark算子之Aggregate
Aggregate函数
一、源码定义
/**
* Aggregate the elements of each partition, and then the results for all the partitions, using
* given combine functions and a neutral "zero value". This function can return a different result
* type, U, than the type of this RDD, T. Thus, we need one operation for merging a T into an U
* and one operation for merging two U's, as in scala.TraversableOnce. Both of these functions are
* allowed to modify and return their first argument instead of creating a new U to avoid memory
* allocation.
*
* @param zeroValue the initial value for the accumulated result of each partition for the
* `seqOp` operator, and also the initial value for the combine results from
* different partitions for the `combOp` operator - this will typically be the
* neutral element (e.g. `Nil` for list concatenation or `0` for summation)
* @param seqOp an operator used to accumulate results within a partition
* @param combOp an associative operator used to combine results from different partitions
*/
def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U = withScope {
// Clone the zero value since we will also be serializing it as part of tasks
var jobResult = Utils.clone(zeroValue, sc.env.serializer.newInstance())
val cleanSeqOp = sc.clean(seqOp)
val cleanCombOp = sc.clean(combOp)
val aggregatePartition = (it: Iterator[T]) => it.aggregate(zeroValue)(cleanSeqOp, cleanCombOp)
val mergeResult = (index: Int, taskResult: U) => jobResult = combOp(jobResult, taskResult)
sc.runJob(this, aggregatePartition, mergeResult)
jobResult
}
首先,大致解释一下源码中的定义:
因为通常为我们的spark程序计算是分布式的,所以我们通常需要聚合的数据都分部在不同的分区,不同的机器上。
该函数它会首先对每个分区内的数据基于初始值进行一个首次聚合,然后将每个分区聚合的结果,通过使用给定的聚合函数,再次基于初始值进行分区之间的聚合,并且最终干函数的返回结果的类型,可以是与该RDD的类型相同。什么意思呢,其实这样说,还是有点蒙圈的,下面详细道来。
从源码中可以看出,该函数是一个柯里化函数,它需要接受一共三个参数,分别是:
- zeroValue:U
这个值代表的是我们需要设置的初始值,该初始值可以是不与原RDD的元素的类型相同,可以是Int,String,元组等等任何我们所需要的类型,根据自己的需求来,为了方便后面的表示,假设我把它定义为数值类型的元组(0,0),注意,这里必须是具体的值,并非函数
- seqOp: (U, T) => U
这里需要定义一个函数,注意,是函数,U的类型与我们在第一步中定义的初始值得类型相同,所以,这里的U指的就是(Int,Int)类型
这里的T代表的即为RDD中每个元素的值。
该函数的功能是,在每个分区内遍历每个元素,将每个元素与U进行聚合,具体的聚合方式,我们可以自定义,不过有一点需要注意,这里聚合的时候依然要基于初始值来进行计算
- combOp: (U, U) => U
这里同样需要定义一个函数,这里的U即为每个分区内聚合之后的结果,如上,上一步中的U为(Int,Int)类型,则这里的U也为该类型
该函数的主要作用就是对每个分区聚合之后的结果进行再次合并,即分区之间的合并,但是,同样,在合并的开始,也是要基于初始进行合并,其实这里我们可以发现,这里U的类型是与初始值的类型是相同的。
上面啰里啰嗦的说了这么所,感觉还是不太直观,上代码瞧瞧:
案例1:求取给定RDD的平均数
object TestAggreate {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[2]")
val sc = new SparkContext(conf)
sc.setLogLevel("warn")
val rdd1 = sc.parallelize(Seq(("a", 2), ("a", 5), ("a", 4), ("b", 5), ("c", 3), ("b", 3), ("c", 6), ("a", 8)), 1)
val r1 = rdd1.aggregate((0, 0))(
(u, c) => (u._1 + 1, u._2 + c._2),
(r1, r2) => (r1._1 + r2._1, r1._2 + r2._2)
)
println(r1)
sc.stop()
}
}
这里先给出运行结果,再来解释:

首先我们需要确定,该RDD的分区数为1,也就是说所有的数据都是在一个分区内进行计算,其次该RDD的类型是RDD[(String,Int)],我的目标是求该RDD总个数以及第二个值得总和
1、首先定义初始值,该例子中我定义为了(0,0),是一个(Int,Int)类型的,我准备第一个0代表计数,第二个0代表对每个元素进行求和
2、(u,c),这个函数,这里的u类型就是(Int,Int)类型,c指的就是RDD中的每个元素,每遍历一个元素c,u的第一个元素就会加1,也就是u._1 + 1,同时u的第二个元素会对c的第二个元素进行累加,也就是u._2 + c._2,不过这里的累加都是要基于初始值进行累加的,顺序是这样的:
第一次 0+1,0+2
第二次 1+1,2+5
第三次 2+1,7+4
第四次 3+1,11+5
第五次 4+1,16+3
第六次 5+1,19+3
第七次 6+1,22+6
第八次 7+1,28+8
最终结果就是(8,36)
3、(r1.r2),该函数是实现每个分区内的数据进行合并,因为这里只有一个分区,所以只是分区0与另外一个空分区进行合并。
这里如果我们将分区数设置为超过1个的情况下,会怎样呢,来看下:
bject TestAggreate {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[2]")
val sc = new SparkContext(conf)
sc.setLogLevel("warn")
val rdd1 = sc.parallelize(Seq(("a", 2), ("a", 5), ("a", 4), ("b", 5), ("c", 3), ("b", 3), ("c", 6), ("a", 8)), 4)
val r1 = rdd1.aggregate((0, 0))(
(u, c) => (u._1 + 1, u._2 + c._2),
(r1, r2) => (r1._1 + r2._1, r1._2 + r2._2)
)
println(r1)
sc.stop()
}
}
这种情况下,我们将RDD分在了四个分区内,每个分区内分配两个数据,具体每个分区内有哪几个元素,可以这样查看:
rdd1.foreachPartition(part => {
val partitionId = TaskContext.getPartitionId()
part.foreach(x => {
println((partitionId, x._1, x._2))
})
})

从上面可以看出,分区数据分别是存在了part0((a,2),(a,5)),part1((a,4),(b,5)),part1((c,3),(b,3)),part3((c,6),(a,8)),这种情况下的合并过程是这样的:
1、每个分区内合并,结果就是 part0(0+1+1,0+2+5) part1(0+1+1,0+4+5) part2(0+1+1,0+3+3) part3(0+1+1,0+6+8)
2、(part0,part1) => (part0._1 + part1._1,part0._2+part1._2),然后使用该结果,在依次与part2,part3进行合并,结果就为(0+2+2+2+2,0+7+9+6+14),结果(8,36)
这里我在测试的过程中发现一个问题,就是说在分区数大于1的情况下,当我最后将分区合并的函数中的聚合过程,相互颠倒过来的话,也就是,正常,我应该得到(8,36),但是我聚合的时候想得到(36,8)这个结果,下面这段代码:
val r1 = rdd1.aggregate((0, 0))(
(u, c) => (u._1 + 1, u._2 + c._2),
(r1, r2) => (r1._2 + r2._2, r1._1 + r2._1)
)
上面标红的代码,我颠倒了顺序,我的预期的得到(36,8),但是结果却是随机产生的结果,像这样:


上面是执行了两次,产生了两次不同的结果,但是显然是错误的。但是具体它是怎么计算出来的,博主现在目前还没有研究出来。
案例2:求和
该案例主要是测试一下初始值的变化对结果产生的影响,进一步证明,不管是在分区内进行聚合还是分区之间进行聚合的时候,都会使用到初始值,案例1中的初始值我们都设置的是0,此时我将其设置成2在来看看结果,测试代码:
object TestAggreate {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[2]")
val sc = new SparkContext(conf)
sc.setLogLevel("warn")
val rdd1 = sc.parallelize(Seq(("a", 2), ("a", 5), ("a", 4), ("b", 5), ("c", 3), ("b", 3), ("c", 6), ("a", 8)), 4)
val r1 = rdd1.aggregate((0))(
(u, c) => (u + c._2),
(r1, r2) => (r1 + r2)
)
println(r1)
sc.stop()
}
}
结果:(46),但是实际之和加起来是36,显然多出了10,这个10是怎么来的呢?
计算方式如下:
1、首先这里是4个分区,每个分区进行聚合,而之前说过,分区内聚合都是要以初始值为基准的,也就是说要在初始值得基础上进行相加:
part0 (2+2+5)
part1(2+4+5)
part2(2+3+3)
part3(2+6+8)
2、其次是分区之间的聚合,分区之间的聚合也是要在初始值的基础上相加的,即
2+part0+part1+part2+part3
结果即为46,
所以说,如果我们想要得到预想的结果,对于该函数生成的结果还要减去如下数:
result-initValue*(partitions+1)
总结一下:
该函数是spark中的一个高性能的算子,它实现了先进性分区内的聚合之后在进行了对每个分区的聚合结果再次进行聚合的操作,这样的在大数据量的情况下,大大减少了数据在各个节点之间不必要的网络IO,大大提升了性能,相比于groupBy的函数,在特定情况下,性能提升数十倍不止,不过在使用的过程中一定要对该函数所对应的每个参数的含义了如指掌,这样运用起来才能得心应手。
spark算子之Aggregate的更多相关文章
- spark算子篇-aggregate 系列
aggregate aggregate 是比较常用的 行动 操作,不是很好懂,这里做个解释. aggregate(zeroValue, seqOp, combOp) zeroValue 是一个初始值, ...
- (转)Spark 算子系列文章
http://lxw1234.com/archives/2015/07/363.htm Spark算子:RDD基本转换操作(1)–map.flagMap.distinct Spark算子:RDD创建操 ...
- Spark算子总结及案例
spark算子大致上可分三大类算子: 1.Value数据类型的Transformation算子,这种变换不触发提交作业,针对处理的数据项是Value型的数据. 2.Key-Value数据类型的Tran ...
- Spark算子总结(带案例)
Spark算子总结(带案例) spark算子大致上可分三大类算子: 1.Value数据类型的Transformation算子,这种变换不触发提交作业,针对处理的数据项是Value型的数据. 2.Key ...
- UserView--第二种方式(避免第一种方式Set饱和),基于Spark算子的java代码实现
UserView--第二种方式(避免第一种方式Set饱和),基于Spark算子的java代码实现 测试数据 java代码 package com.hzf.spark.study; import ...
- UserView--第一种方式set去重,基于Spark算子的java代码实现
UserView--第一种方式set去重,基于Spark算子的java代码实现 测试数据 java代码 package com.hzf.spark.study; import java.util.Ha ...
- spark算子之DataFrame和DataSet
前言 传统的RDD相对于mapreduce和storm提供了丰富强大的算子.在spark慢慢步入DataFrame到DataSet的今天,在算子的类型基本不变的情况下,这两个数据集提供了更为强大的的功 ...
- Spark算子---实战应用
Spark算子实战应用 数据集 :http://grouplens.org/datasets/movielens/ MovieLens 1M Datase 相关数据文件 : users.dat --- ...
- spark算子集锦
Spark 是大数据领域的一大利器,花时间总结了一下 Spark 常用算子,正所谓温故而知新. Spark 算子按照功能分,可以分成两大类:transform 和 action.Transform 不 ...
随机推荐
- jq的ajax请求更改为axios请求时零碎总结
#老版切新版更改处----ajax 更改为 axios //ajax$.ajax({ type: 'POST', url: url, data: data, success: success, dat ...
- mongoose 与 mylab 的使用 (1)
1.引入mongoose 模块 const mongoose = require('mongoose'); 2.连接数据库 //连接数据库 mongoose.connect( db, {useNew ...
- 1、selenium 8大元素定位方式
元素定位方式: id name css class_name tag_name partial_link link_text : driver. find_element_by_link_text(& ...
- Delphi UTF编码 UTF8Encode、UTF8Decode、URLEncode、URLDecode
一.URL简介 URL是网页的地址,比如 http://www.cnblogs.com.Web 浏览器通过 URL 从 web 服务器请求页面. 由于URL字符串常常会包含非ASCII字符 ...
- PHP copy() 函数
定义和用法 copy() 函数复制文件. 该函数如果成功则返回 TRUE,如果失败则返回 FALSE. 语法 copy(file,to_file) 参数 描述 file 必需.规定要复制的文件. to ...
- BZOJ 5415: [Noi2018]归程(kruskal重构树)
解题思路 \(NOI2018\)的\(Day1\) \(T1\),当时打网络赛的时候不会做.学了一下\(kruskal\)重构树后发现问题迎刃而解了.根据\(kruskal\)的性质,如果要找从\(u ...
- 小程序 页面传值 json 被截断
因此解决这个报错的方法是将要传输的数据用 encodeURIComponent()函数(可把字符串作为 URI 组件进行编码) 先进行编码, 传输过去后再通过decodeURIComponent()函 ...
- System.IO.Path 文件名、路径、扩展名处理
string filePath =@"E:/Randy0528/中文目录/JustTest.rar"; 更改路径字符串的扩展名.System.IO.Path.ChangeExten ...
- Mac启动时:boot task failed:fsck-safe处理办法
mac系统启动到一半,然后突然关机,查看启动信息发现:boot task failed:fsck-safe 处理方法,clover启动的时候按空格,然后选择sigle mode. 进入使用命令:fsc ...
- django的model继承abstract,proxy,managed
https://www.cnblogs.com/wangwei916797941/p/9525127.html?from=timeline