2019-04-20

关键字: Spark 的 agrregate 作用、Scala 的 aggregate 是什么

Spark 编程中的 aggregate 方法还是比较常用的。本篇文章站在初学者的角度以大白话的形式来讲解一下 aggregate 方法。


aggregate 方法是一个聚合函数,接受多个输入,并按照一定的规则运算以后输出一个结果值。

aggregate 在哪

aggregate 方法是 Spark 编程模型 RDD 类( org.apache.spark.RDD ) 中定义的一个公有方法。它的方法声明如下

   def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U = withScope {

     // ...

   }

aggregate 的参数是什么意思

然后我们一块一块来学习这个方法的声明。其实这小节讲的,都是 Scala 的语法知识。

def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U = withScope {

首先看到的是 “泛型” 声明。懂 Java 的同学直接把这个 " [U: ClassTag] " 理解成是一个泛型声明就好了。如果您不是很熟悉 Java 语言,那我们只需要知道这个 U 表示我们的 aggregate 方法只能接受某一种类型的输入值,至于到底是哪种类型,要看您在具体调用的时候给了什么类型。

def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U = withScope {

然后我们来看看 aggregate 的参数列表。明显这个 aggregate 方法是一个柯里化函数。柯里化的知识不在本篇文章讨论的范围之内。如果您还不了解柯里化的概念,那在这里简单地理解为是通过多个圆括号来接受多个输入参数就可以了

然后我们来看看第 1 部分,即上面蓝色加粗的 " (zeroValue: U) " 。这个表示它接受一个任意类型的输入参数,变量名为 zeroValue 。这个值就是初值,至于这个初值的作用,姑且不用理会,等到下一小节通过实例来讲解会更明了,在这里只需要记住它是一个 “只使用一次” 的值就好了。

第 2 部分,我们还可以再把它拆分一下,因为它里面其实有两个参数。笔者认为 Scala 语法在定义多个参数时,辨识度比较弱,不睁大眼睛仔细看,很难确定它到底有几个参数。

首先是第 1 个参数 " seqOp: (U, T) => U " 它是一个函数类型,以一个输入为任意两个类型 U, T 而输出为 U 类型的函数作为参数。这个函数会先被执行。这个参数函数的作用是为每一个分片( slice )中的数据遍历应用一次函数。换句话说就是假设我们的输入数据集( RDD )有 1 个分片,则只有一个 seqOp 函数在运行,假设有 3 个分片,则有三个 seqOp 函数在运行。可能有点难以理解,不过没关系,到后面结合实例就很容易理解了。

另一个参数 " combOp: (U, U) => U " 接受的也是一个函数类型,以输入为任意类型的两个输入参数而输出为一个与输入同类型的值的函数作为参数。这个函数会在上面那个函数执行以后再执行。这个参数函数的输入数据来自于第一个参数函数的输出结果,这个函数仅会执行 1 次,它是用来最终聚合结果用的。同样这里搞不懂没关系,下一小节的实例部分保证让您明白。

def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U = withScope {

最后是上面这个红色加粗的 " : U " 它是 aggregate 方法的返回值类型,也是泛型表示。

对了,最后还有一个 " withScope ",这个就不介绍了,因为笔者也不知道它是干嘛的,哈哈哈哈。反正对我们理解这个方法也没什么影响。

aggregate 正确的使用姿势

我们直接在 spark-shell 中来演示实例了。这里以两个小例子来演示,一个是不带分片的 RDD ,另一个则是带 3 个分片的 RDD 。

首先我们来创建一个 RDD

scala> val rdd1 = sc.parallelize(1 to 5)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[] at parallelize at <console>:24

scala> rdd1 collect
warning: there was one feature warning; re-run with -feature for details
res24: Array[Int] = Array(1, 2, 3, 4, 5)

这个 RDD 仅有 1 个分片,包含 5 个数据: 1, 2, 3, 4, 5 。

然后我们来应用一下 aggregate 方法。

哦,不对,在使用 aggregate 之前,我们还是先定义两个要给 aggregate 当作输入参数的函数吧。

scala> :paste
// Entering paste mode (ctrl-D to finish) def pfun1(p1: Int, p2: Int): Int = { p1 * p2 } // Exiting paste mode, now interpreting. pfun1: (p1: Int, p2: Int)Int scala>

首先来定义第 1 个函数,即等下要被当成 seqOp 的形参使用的函数。在上一小节我们知道 seqOp 函数是一个输入类型为 U, T 类型而输出为 U 类型的函数。但是在这里,因为我们的 RDD 只包含一个 Int 类型数据,所以这里的 seqOp 的两个输入参数都是 Int 类型的,这是没毛病的哦!然后这个函数的返回类型也为 Int 。我们这个函数的作用就是将输入的参数 p1 , p2 求积以后返回。

scala> :paste
// Entering paste mode (ctrl-D to finish) def pfun2(p3: Int, p4: Int): Int = { p3 + p4 } // Exiting paste mode, now interpreting. pfun2: (p3: Int, p4: Int)Int scala>

接着是第 2 个函数。就不再解释什么了。

然后终于可以开始应用我们的 aggregate 方法了。

scala> rdd1.aggregate(3)(pfun1, pfun2)
res25: Int = scala>

输出结果是 363 !这个结果是怎么算出来的呢?

首先我们的 zeroValue 即初值是 3 。然后通过上面小节的介绍,我们知道首先会应用 pfun1 函数,因为我们这个 RDD 只有 1 个分片,所以整个运算过程只会有一次 pfun1 函数调用。它的计算过程如下:

首先用初值 3 作为 pfun1 的参数 p1 ,然后再用 RDD 中的第 1 个值,即 1 作为 pfun1 的参数 p2 。由此我们可以得到第一个计算值为 3 * 1 = 3 。接着这个结果 3 被当成 p1 参数传入,RDD 中的第 2 个值即 2 被当成 p2 传入,由此得到第二个计算结果为 3 * 2 = 6 。以此类推,整个 pfun1 函数执行完成以后,得到的结果是   * * * * * =  。这个 pfun1 的应用过程有点像是 “在 RDD 中滑动计算” 。

在 aggregate 方法的第 1 个参数函数 pfun1 执行完毕以后,我们得到了结果值 360 。于是,这个时候就要开始执行第 2 个参数函数 pfun2 了。

pfun2 的执行过程与 pfun1 是差不多的,同样会将 zeroValue 作为第一次运算的参数传入,在这里即是将 zeroValue 即 3 当成 p3 参数传入,然后是将 pfun1 的结果  当成 p4 参数传入,由此得到计算结果为 363 。因为 pfun1 仅有一个结果值,所以整个 aggregate 过程就计算完毕了,最终的结果值就是

怎么样?相信您已经完全明白 aggregate 方法的的作用与用法了吧。下面再贴一个有多个分片的 RDD 的示例。

scala> val rdd2 = sc.makeRDD(1 to 10, 3)
rdd2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[] at makeRDD at <console>: scala> rdd2.getNumPartitions
res26: Int = scala> rdd2.foreachPartition(myprint)
, , ,
, , ,
, , , ,

这里定义了一个拥有 3 个分片的 RDD 。然后 aggregate 的两个函数参数仍然是使用上面定义的 pfun1 与 pfun2 。

scala> rdd2.aggregate(2)(pfun1, pfun2)
res29: Int =

结果是 10334 。怎么来的呢?

因为前面小节有提到 seqOp 函数,即这里的 pfun1 函数会分别在 RDD 的每个分片中应用一次,所以这里 pfun1 的计算过程为

 *  *  *        =
2 * * * =
* * * * =

标橙的为 zeroValue 。

在这里 pfun1 的输出结果有 3 个值。然后就来应用 combOp 即这里的 pfun2

 +  +  +   = 

所以,结果就是 10334 咯!


轻松理解 Spark 的 aggregate 方法的更多相关文章

  1. 《深入理解Spark:核心思想与源码分析》(前言及第1章)

    自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售 ...

  2. 《深入理解Spark:核心思想与源码分析》一书正式出版上市

    自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售 ...

  3. 《深入理解Spark:核心思想与源码分析》正式出版上市

    自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售 ...

  4. 万字长文,带你轻松学习 Spark

    大家好,我是大D. 今天给大家分享一篇 Spark 核心知识点的梳理,对知识点的讲解秉承着能用图解的就不照本宣科地陈述,力求精简.通俗易懂.希望能为新手的入门学习扫清障碍,从基础概念入手.再到原理深入 ...

  5. 《深入理解Spark:核心思想与源码分析》——SparkContext的初始化(叔篇)——TaskScheduler的启动

    <深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...

  6. 《深入理解Spark:核心思想与源码分析》(第2章)

    <深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...

  7. 轻松理解JVM的分代模型

    前言 上篇文章我们一起对jvm的内存模型有了比较清晰的认识,小伙伴们可以参考JVM内存模型不再是秘密这篇文章做一个复习. 本篇文章我们将针对jvm堆内存的分代模型做一个详细的解析,和大家一起轻松理解j ...

  8. 轻松理解UML用例图时序图类图的教程

    摘自https://zhuanlan.zhihu.com/p/29874146 写在前面 当你老大扔给你这样的图,或者你需要完成某些功能而去看文档的时候发现以下类似这样的图会不会不(一)知(脸)所(懵 ...

  9. 轻松理解 Spring AOP

    目录 Spring AOP 简介 Spring AOP 的基本概念 面向切面编程 AOP 的目的 AOP 术语和流程 术语 流程 五大通知执行顺序 例子 图例 实际的代码 使用 Spring AOP ...

随机推荐

  1. Scss预处理器的使用总结

    变量 .嵌套.Mixin混合.function函数.插值 变量及文件导入 通过$定义变量 $white:#fff; $black:#000; 变量引用 .containner{ color:$blac ...

  2. json属性名必须加引号的讨论

    优质参考资料: 1.https://blog.csdn.net/Goskalrie/article/details/52151175 2.https://blog.csdn.net/weixin_42 ...

  3. Program Thread 和 Process的不同点

    Thread is for execution Kernel level thread, physical parallelism Cores Divide work amount of physic ...

  4. python迭代器与生成器及yield

    一.迭代器(itertor) 1.可迭代: 在Python中如果一个对象有__iter__()方法或__getitem__()方法,则称这个对象是可迭代的(iterable). 其中__iter__( ...

  5. Vue开发插件

    (一)Vue.js的插件应该有一个公开方法:install. 这个方法的第一个参数是Vue构造器,第二个参数是一个可选的选项对象,一般是如下操作: MyPlugin.install = functio ...

  6. 通过JS动态的修改HTML元素的样式和增添标签元素等

    一. 通过JS动态的修改HTML元素的样式   1. 要想在js中动态的修改HTML元素的样式,首先需要写document,    document我们称之为文档对象,这个对象中保存了当前网页中所有的 ...

  7. Spring AOP 整理笔记

    一.AOP概念 AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. 利用AOP可以对业务逻辑的各 ...

  8. tomat 欢迎页面设置在WEB-INF目录下时不显示问题

    <?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://w ...

  9. 结对编程项目总结 by:陈宏伟&刘益

    结对编程项目在欢快的国庆假期中也顺利结束了.从最初拿到结对编程项目的思考,再到一步一步实现,中间经历了一个漫长的过程.在我和队友的多次协商下,最终我们还是选择使用基于python来实现这一次结对编程项 ...

  10. Jmeter、Java当double显示的数字过长时取消科学计数法显示

    今日,由于项目需要,使用Jmeter发送查询账户并不保存余额,经过交易后,进行运算后再次比对余额. 实施过程中获取了余额字段并赋值给一个double变量.变量进行运算后再与交易后的账户余额进行比对.这 ...