本质上在Actions算子中通过SparkContext执行提交作业的runJob操作,触发了RDD DAG的执行。 
根据Action算子的输出空间将Action算子进行分类:无输出、 HDFS、 Scala集合和数据类型。

无输出

foreach

对RDD中的每个元素都应用f函数操作,不返回RDD和Array,而是返回Uint。 

图中,foreach算子通过用户自定义函数对每个数据项进行操作。 本例中自定义函数为println,控制台打印所有数据项。

源码:

  1. /**
  2. * Applies a function f to all elements of this RDD.
  3. */
  4. def foreach(f: T => Unit) {
  5. val cleanF = sc.clean(f)
  6. sc.runJob(this, (iter: Iterator[T]) => iter.foreach(cleanF))
  7. }

HDFS

(1)saveAsTextFile

函数将数据输出,存储到HDFS的指定目录。将RDD中的每个元素映射转变为(Null,x.toString),然后再将其写入HDFS。 

图中,左侧的方框代表RDD分区,右侧方框代表HDFS的Block。 通过函数将RDD的每个分区存储为HDFS中的一个Block。

源码:

  1. /**
  2. * Save this RDD as a text file, using string representations of elements.
  3. */
  4. def saveAsTextFile(path: String) {
  5. // https://issues.apache.org/jira/browse/SPARK-2075
  6. //
  7. // NullWritable is a `Comparable` in Hadoop 1.+, so the compiler cannot find an implicit
  8. // Ordering for it and will use the default `null`. However, it's a `Comparable[NullWritable]`
  9. // in Hadoop 2.+, so the compiler will call the implicit `Ordering.ordered` method to create an
  10. // Ordering for `NullWritable`. That's why the compiler will generate different anonymous
  11. // classes for `saveAsTextFile` in Hadoop 1.+ and Hadoop 2.+.
  12. //
  13. // Therefore, here we provide an explicit Ordering `null` to make sure the compiler generate
  14. // same bytecodes for `saveAsTextFile`.
  15. val nullWritableClassTag = implicitly[ClassTag[NullWritable]]
  16. val textClassTag = implicitly[ClassTag[Text]]
  17. val r = this.mapPartitions { iter =>
  18. val text = new Text()
  19. iter.map { x =>
  20. text.set(x.toString)
  21. (NullWritable.get(), text)
  22. }
  23. }
  24. RDD.rddToPairRDDFunctions(r)(nullWritableClassTag, textClassTag, null)
  25. .saveAsHadoopFile[TextOutputFormat[NullWritable, Text]](path)
  26. }
  27. /**
  28. * Save this RDD as a compressed text file, using string representations of elements.
  29. */
  30. def saveAsTextFile(path: String, codec: Class[_ <: CompressionCodec]) {
  31. // https://issues.apache.org/jira/browse/SPARK-2075
  32. val nullWritableClassTag = implicitly[ClassTag[NullWritable]]
  33. val textClassTag = implicitly[ClassTag[Text]]
  34. val r = this.mapPartitions { iter =>
  35. val text = new Text()
  36. iter.map { x =>
  37. text.set(x.toString)
  38. (NullWritable.get(), text)
  39. }
  40. }
  41. RDD.rddToPairRDDFunctions(r)(nullWritableClassTag, textClassTag, null)
  42. .saveAsHadoopFile[TextOutputFormat[NullWritable, Text]](path, codec)
  43. }

(2)saveAsObjectFile

saveAsObjectFile将分区中的每10个元素组成一个Array,然后将这个Array序列化,映射为(Null,BytesWritable(Y))的元素,写入HDFS为SequenceFile的格式。

图中,左侧方框代表RDD分区,右侧方框代表HDFS的Block。 通过函数将RDD的每个分区存储为HDFS上的一个Block。

源码:

  1. /**
  2. * Save this RDD as a SequenceFile of serialized objects.
  3. */
  4. def saveAsObjectFile(path: String) {
  5. this.mapPartitions(iter => iter.grouped(10).map(_.toArray))
  6. .map(x => (NullWritable.get(), new BytesWritable(Utils.serialize(x))))
  7. .saveAsSequenceFile(path)
  8. }

Scala集合和数据类型

(1)collect

collect相当于toArray,toArray已经过时不推荐使用,collect将分布式的RDD返回为一个单机的scala Array数组。 在这个数组上运用scala的函数式操作。

图中,左侧方框代表RDD分区,右侧方框代表单机内存中的数组。通过函数操作,将结果返回到Driver程序所在的节点,以数组形式存储。

源码:

  1. /**
  2. * Return an array that contains all of the elements in this RDD.
  3. */
  4. def collect(): Array[T] = {
  5. val results = sc.runJob(this, (iter: Iterator[T]) => iter.toArray)
  6. Array.concat(results: _*)
  7. }

(2)collectAsMap

collectAsMap对(K,V)型的RDD数据返回一个单机HashMap。对于重复K的RDD元素,后面的元素覆盖前面的元素。 

图中,左侧方框代表RDD分区,右侧方框代表单机数组。数据通过collectAsMap函数返回给Driver程序计算结果,结果以HashMap形式存储。

源码:

  1. /**
  2. * Return the key-value pairs in this RDD to the master as a Map.
  3. *
  4. * Warning: this doesn't return a multimap (so if you have multiple values to the same key, only
  5. *          one value per key is preserved in the map returned)
  6. */
  7. def collectAsMap(): Map[K, V] = {
  8. val data = self.collect()
  9. val map = new mutable.HashMap[K, V]
  10. map.sizeHint(data.length)
  11. data.foreach { pair => map.put(pair._1, pair._2) }
  12. map
  13. }

(3)reduceByKeyLocally

实现的是先reduce再collectAsMap的功能,先对RDD的整体进行reduce操作,然后再收集所有结果返回为一个HashMap。

源码:

  1. /**
  2. * Merge the values for each key using an associative reduce function, but return the results
  3. * immediately to the master as a Map. This will also perform the merging locally on each mapper
  4. * before sending results to a reducer, similarly to a "combiner" in MapReduce.
  5. */
  6. def reduceByKeyLocally(func: (V, V) => V): Map[K, V] = {
  7. if (keyClass.isArray) {
  8. throw new SparkException("reduceByKeyLocally() does not support array keys")
  9. }
  10. val reducePartition = (iter: Iterator[(K, V)]) => {
  11. val map = new JHashMap[K, V]
  12. iter.foreach { pair =>
  13. val old = map.get(pair._1)
  14. map.put(pair._1, if (old == null) pair._2 else func(old, pair._2))
  15. }
  16. Iterator(map)
  17. } : Iterator[JHashMap[K, V]]
  18. val mergeMaps = (m1: JHashMap[K, V], m2: JHashMap[K, V]) => {
  19. m2.foreach { pair =>
  20. val old = m1.get(pair._1)
  21. m1.put(pair._1, if (old == null) pair._2 else func(old, pair._2))
  22. }
  23. m1
  24. } : JHashMap[K, V]
  25. self.mapPartitions(reducePartition).reduce(mergeMaps)
  26. }

(4)lookup

Lookup函数对(Key,Value)型的RDD操作,返回指定Key对应的元素形成的Seq。这个函数处理优化的部分在于,如果这个RDD包含分区器,则只会对应处理K所在的分区,然后返回由(K,V)形成的Seq。如果RDD不包含分区器,则需要对全RDD元素进行暴力扫描处理,搜索指定K对应的元素。

图中,左侧方框代表RDD分区,右侧方框代表Seq,最后结果返回到Driver所在节点的应用中。

源码:

  1. /**
  2. * Return the list of values in the RDD for key `key`. This operation is done efficiently if the
  3. * RDD has a known partitioner by only searching the partition that the key maps to.
  4. */
  5. def lookup(key: K): Seq[V] = {
  6. self.partitioner match {
  7. case Some(p) =>
  8. val index = p.getPartition(key)
  9. val process = (it: Iterator[(K, V)]) => {
  10. val buf = new ArrayBuffer[V]
  11. for (pair <- it if pair._1 == key) {
  12. buf += pair._2
  13. }
  14. buf
  15. } : Seq[V]
  16. val res = self.context.runJob(self, process, Array(index), false)
  17. res(0)
  18. case None =>
  19. self.filter(_._1 == key).map(_._2).collect()
  20. }
  21. }

(5)count

count返回整个RDD的元素个数。 

图中,返回数据的个数为5。一个方块代表一个RDD分区。

源码:

  1. /**
  2. * Return the number of elements in the RDD.
  3. */
  4. def count(): Long = sc.runJob(this, Utils.getIteratorSize _).sum

(6)top

top可返回最大的k个元素。 
相近函数说明:

  • top返回最大的k个元素。
  • take返回最小的k个元素。
  • takeOrdered返回最小的k个元素, 并且在返回的数组中保持元素的顺序。
  • first相当于top( 1) 返回整个RDD中的前k个元素, 可以定义排序的方式Ordering[T]。返回的是一个含前k个元素的数组。

源码:

  1. /**
  2. * Returns the top k (largest) elements from this RDD as defined by the specified
  3. * implicit Ordering[T]. This does the opposite of [[takeOrdered]]. For example:
  4. * {{{
  5. *   sc.parallelize(Seq(10, 4, 2, 12, 3)).top(1)
  6. *   // returns Array(12)
  7. *
  8. *   sc.parallelize(Seq(2, 3, 4, 5, 6)).top(2)
  9. *   // returns Array(6, 5)
  10. * }}}
  11. *
  12. * @param num k, the number of top elements to return
  13. * @param ord the implicit ordering for T
  14. * @return an array of top elements
  15. */
  16. def top(num: Int)(implicit ord: Ordering[T]): Array[T] = takeOrdered(num)(ord.reverse)

(7)reduce

reduce函数相当于对RDD中的元素进行reduceLeft函数的操作。 
reduceLeft先对两个元素

  1. /**
  2. * Reduces the elements of this RDD using the specified commutative and
  3. * associative binary operator.
  4. */
  5. def reduce(f: (T, T) => T): T = {
  6. val cleanF = sc.clean(f)
  7. val reducePartition: Iterator[T] => Option[T] = iter => {
  8. if (iter.hasNext) {
  9. Some(iter.reduceLeft(cleanF))
  10. } else {
  11. None
  12. }
  13. }
  14. var jobResult: Option[T] = None
  15. val mergeResult = (index: Int, taskResult: Option[T]) => {
  16. if (taskResult.isDefined) {
  17. jobResult = jobResult match {
  18. case Some(value) => Some(f(value, taskResult.get))
  19. case None => taskResult
  20. }
  21. }
  22. }
  23. sc.runJob(this, reducePartition, mergeResult)
  24. // Get the final result out of our Option, or throw an exception if the RDD was empty
  25. jobResult.getOrElse(throw new UnsupportedOperationException("empty collection"))
  26. }

(8)fold

fold和reduce的原理相同,但是与reduce不同,相当于每个reduce时,迭代器取的第一个元素是zeroValue。 

图中,通过用户自定义函数进行fold运算,图中的一个方框代表一个RDD分区。

源码:

  1. /**
  2. * Aggregate the elements of each partition, and then the results for all the partitions, using a
  3. * given associative function and a neutral "zero value". The function op(t1, t2) is allowed to
  4. * modify t1 and return it as its result value to avoid object allocation; however, it should not
  5. * modify t2.
  6. */
  7. def fold(zeroValue: T)(op: (T, T) => T): T = {
  8. // Clone the zero value since we will also be serializing it as part of tasks
  9. var jobResult = Utils.clone(zeroValue, sc.env.closureSerializer.newInstance())
  10. val cleanOp = sc.clean(op)
  11. val foldPartition = (iter: Iterator[T]) => iter.fold(zeroValue)(cleanOp)
  12. val mergeResult = (index: Int, taskResult: T) => jobResult = op(jobResult, taskResult)
  13. sc.runJob(this, foldPartition, mergeResult)
  14. jobResult
  15. }

(9)aggregate

aggregate先对每个分区的所有元素进行aggregate操作,再对分区的结果进行fold操作。 
aggreagate与fold和reduce的不同之处在于,aggregate相当于采用归并的方式进行数据聚集,这种聚集是并行化的。 而在fold和reduce函数的运算过程中,每个分区中需要进行串行处理,每个分区串行计算完结果,结果再按之前的方式进行聚集,并返回最终聚集结果。

图中,通过用户自定义函数对RDD 进行aggregate的聚集操作,图中的每个方框代表一个RDD分区。

源码:

  1. /**
  2. * Aggregate the elements of each partition, and then the results for all the partitions, using
  3. * given combine functions and a neutral "zero value". This function can return a different result
  4. * type, U, than the type of this RDD, T. Thus, we need one operation for merging a T into an U
  5. * and one operation for merging two U's, as in scala.TraversableOnce. Both of these functions are
  6. * allowed to modify and return their first argument instead of creating a new U to avoid memory
  7. * allocation.
  8. */
  9. def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U = {
  10. // Clone the zero value since we will also be serializing it as part of tasks
  11. var jobResult = Utils.clone(zeroValue, sc.env.closureSerializer.newInstance())
  12. val cleanSeqOp = sc.clean(seqOp)
  13. val cleanCombOp = sc.clean(combOp)
  14. val aggregatePartition = (it: Iterator[T]) => it.aggregate(zeroValue)(cleanSeqOp, cleanCombOp)
  15. val mergeResult = (index: Int, taskResult: U) => jobResult = combOp(jobResult, taskResult)
  16. sc.runJob(this, aggregatePartition, mergeResult)
  17. jobResult
  18. }

原文链接:http://blog.csdn.net/jasonding1354

RDD之六:Action算子的更多相关文章

  1. 关于spark RDD trans action算子、lineage、宽窄依赖详解

    这篇文章想从spark当初设计时为何提出RDD概念,相对于hadoop,RDD真的能给spark带来何等优势.之前本想开篇是想总体介绍spark,以及环境搭建过程,但个人感觉RDD更为重要 铺垫 在h ...

  2. 【Spark】RDD操作具体解释4——Action算子

    本质上在Actions算子中通过SparkContext运行提交作业的runJob操作,触发了RDD DAG的运行. 依据Action算子的输出空间将Action算子进行分类:无输出. HDFS. S ...

  3. Spark中的各种action算子操作(java版)

    在我看来,Spark编程中的action算子的作用就像一个触发器,用来触发之前的transformation算子.transformation操作具有懒加载的特性,你定义完操作之后并不会立即加载,只有 ...

  4. sparkRDD:第3节 RDD常用的算子操作

    4.      RDD编程API 4.1 RDD的算子分类 Transformation(转换):根据数据集创建一个新的数据集,计算后返回一个新RDD:例如:一个rdd进行map操作后生了一个新的rd ...

  5. Spark_Transformation和Action算子

    Transformation 和 Action 常用算子 ​ 一.Transformation        1.1 map        1.2 filter        1.3 flatMap  ...

  6. 入门大数据---Spark_Transformation和Action算子

    一.Transformation spark 常用的 Transformation 算子如下表: Transformation 算子 Meaning(含义) map(func) 对原 RDD 中每个元 ...

  7. Spark RDD概念学习系列之Pair RDD的action操作

    不多说,直接上干货! Pair RDD的action操作 所有基础RDD 支持的行动操作也都在pair RDD 上可用

  8. 【Spark篇】---Spark中Action算子

    一.前述 Action类算子也是一类算子(函数)叫做行动算子,如foreach,collect,count等.Transformations类算子是延迟执行,Action类算子是触发执行.一个appl ...

  9. 初见spark-02(RDD及其简单算子)

    今天,我们来进入spark学习的第二章,发现有很多事都已经开始变化,生活没有简单的朝自己想去的方向,但是还是需要努力呀,不说鸡汤之类的话了, 开始我们今天的spark的旅程 一.RDD是什么 rdd的 ...

随机推荐

  1. python 安装 scapy windows 10 64bit

    简介: 前段时间装的pypcap做嗅探.打包受阻.因为我都是在windows做的.也要打包到exe给别人用. 所以尝试了一下scapy,也可以嗅探,貌似功能更强大.先用sniff吧. 这个也不是在ve ...

  2. 【转】POJ百道水题列表

    以下是poj百道水题,新手可以考虑从这里刷起 搜索1002 Fire Net1004 Anagrams by Stack1005 Jugs1008 Gnome Tetravex1091 Knight ...

  3. sublime text3的快捷键

    Ctrl + Shift + P    调出命令板(Command Palette) Ctrl + `    调出控制台 Ctrl + D    选择当前光标所在的词并高亮该词所有出现的位置,再次 C ...

  4. IOS多线程编程:概述

    什么是多线程 多线程是一个比较轻量级的方法来实现单个应用程序内多个代码执行路径.从技术角度来看,一个线程就是一个需要管理执行代码的内核级和应用级数据结构组合.内核级结构协助调度线程事件,并抢占式调度一 ...

  5. linux 优化git操作速度

    修改 ssh配置:useDNS:no

  6. vue-cli 添加到生产环境问题总结

    1. 路径问题  部署到生产环境后的实际链接为:   服务器项目路径 + serviceUrl的路径  创建 GlobalConstant.js  分别配置 开发环境和生产环境的 路径  (注:此处生 ...

  7. UITableViewCell的高度与UILabel自适应

    UITableViewCell内部只放了一个UILabel,Cell的高度随着UILabel内容的高度变化而变化,可重写UITableView的委托方法动态调整高度,还要设置UILabel.numbe ...

  8. 玩转TypeScript(5)--环境声明

    环境声明为TypeScript引入了一个作用域,但是对于产生的javaScript程序不会有任何影响.程序员可以使用环境声明来告之TypeScript,一些其他的组将将提供变量的声明.比如,默认情况下 ...

  9. 将Mat类型坐标数据生成pts文件

    前言 获取人脸特征点的坐标信息之后,想要将坐标信息shape保存为pts/asf/txt等文件格式,本文就对此进行实现. 实现过程 1.确定pts文件的书写格式: 以要生成的文件为例,书写格式如下: ...

  10. Django Rest FrameWork 全部API

    Django Rest FrameWork .Requests 请求 客服端发送给服务器的请求 .Responses 响应 rest框架支持响应不同格式的内容 .Views 视图 base基础类视图 ...