大数据技术之_19_Spark学习_02_Spark Core 应用解析+ RDD 概念 + RDD 编程 + 键值对 RDD + 数据读取与保存主要方式 + RDD 编程进阶 + 实际案例演示

大数据技术之_19_Spark学习_02

第1章 RDD 概念

1.1 RDD 为什么会产生

  RDD:Resilient Distributed Dataset 弹性分布式数据集
  RDD 是 Spark 的基石,是实现 Spark 数据处理的核心抽象。那么 RDD 为什么会产生呢?
  Hadoop 的 MapReduce 是一种基于数据集的工作模式,面向数据,这种工作模式一般是从存储上加载数据集,然后操作数据集,最后写入物理存储设备。数据更多面临的是一次性处理。
  MR 的这种方式对数据领域两种常见的操作不是很高效。第一种是迭代式的算法。比如机器学习中 ALS、凸优化梯度下降等。这些都需要基于数据集或者数据集的衍生数据反复查询反复操作。MR 这种模式不太合适,即使多 MR 串行处理,性能和时间也是一个问题。数据的共享依赖于磁盘。另外一种是交互式数据挖掘,MR 显然不擅长。
  MR 中的迭代:
  
  Spark中的迭代:
  
  我们需要一个效率非常快,且能够支持迭代计算和有效数据共享的模型,Spark 应运而生。RDD 是基于工作集的工作模式,更多的是面向工作流。
  但是无论是 MR 还是 RDD 都应该具有类似位置感知、容错和负载均衡等特性。

1.2 RDD 概述

1.2.1 什么是 RDD

  RDD(Resilient Distributed Dataset)叫做分布式数据集,是 Spark 中最基本的数据抽象,它代表一个不可变可分区(分片)里面的元素可并行计算的集合(弹性)。在 Spark 中,对数据的所有操作不外乎创建 RDD、转化已有 RDD 以及调用 RDD 操作进行求值。每个 RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。RDD 可以包含 Python、Java、Scala 中任意类型的对象,甚至可以包含用户自定义的对象。RDD 具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD 允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。
  RDD 支持两种操作:转化操作和行动操作。RDD 的转化操作是返回一个新的 RDD 的操作,比如 map() 和 filter(),而行动操作则是向驱动器程序返回结果或把结果写入外部系统的操作。比如 count() 和 first()。
  Spark 采用 惰性计算模式,RDD 只有第一次在一个行动操作中用到时,才会真正计算。Spark 可以优化整个计算过程。默认情况下,Spark 的 RDD 会在你每次对它们进行行动操作时重新计算。如果想在多个行动操作中重用同一个 RDD,可以使用 RDD.persist() 让 Spark 把这个 RDD 缓存下来。

1.2.2 RDD 的属性

  1) 一组分片(Partition),即数据集的基本组成单位。对于 RDD 来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建 RDD 时指定 RDD 的分片个数,如果没有指定,那么就会采用默认值。默认值就是程序所分配到的 CPU Core 的数目。
  2) 一个计算每个分区的函数。Spark 中 RDD 的计算是以分片为单位的,每个 RDD 都会实现 compute 函数以达到这个目的。compute 函数会对迭代器进行复合,不需要保存每次计算的结果。
  3) RDD 之间的依赖关系。RDD 的每次转换都会生成一个新的 RDD,所以 RDD 之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark 可以通过这个依赖关系重新计算丢失的分区数据,而不是对 RDD 的所有分区进行重新计算。
  4) 一个 Partitioner,即 RDD 的分片函数。当 前Spark 中实现了两种类型的分片函数,一个是基于哈希的 HashPartitioner,另外一个是基于范围的 RangePartitioner。只有对于于 key-value 的 RDD,才会有 Partitioner,非 key-value 的 RDD 的 Parititioner 的值是 None。Partitioner 函数不但决定了 RDD 本身的分片数量,也决定了Parent RDD Shuffle 输出时的分片数量。
  5) 一个列表,存储存取每个 Partition 的优先位置(preferred location)。对于一个 HDFS 文件来说,这个列表保存的就是每个 Partition 所在的块的位置。按照“移动数据不如移动计算”的理念,Spark 在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。
  RDD 是一个应用层面的逻辑概念。一个 RDD 多个分片。RDD 就是一个元数据记录集,记录了 RDD 内存所有的关系数据。
  

1.3 RDD 弹性

[外链图片转存失败(img-vu4eHlEc-1562861616812)(https://s2.ax1x.com/2019/04/26/EnWwXF.png)]
1) 自动进行内存和磁盘数据存储的切换
  Spark 优先把数据放到内存中,如果内存放不下,就会放到磁盘里面,程序进行自动的存储切换。
2) 基于血统的高效容错机制
  在 RDD 进行转换和动作的时候,会形成 RDD 的 Lineage 依赖链,当某一个 RDD 失效的时候,可以通过重新计算上游的 RDD 来重新生成丢失的 RDD 数据。
3) Task 如果失败会自动进行特定次数的重试
  RDD 的计算任务如果运行失败,会自动进行任务的重新计算,默认次数是 4 次。
4) Stage 如果失败会自动进行特定次数的重试
  如果 Job 的某个 Stage 阶段计算失败,框架也会自动进行任务的重新计算,默认次数也是 4 次。
5) Checkpoint 和 Persist 可主动或被动触发
  RDD 可以通过 Persist 持久化将 RDD 缓存到内存或者磁盘,当再次用到该 RDD 时直接读取就行。也可以将 RDD 进行检查点,检查点会将数据存储在 HDFS 中,该 RDD 的所有父 RDD 依赖都会被移除。
6) 数据调度弹性
  Spark 把这个 JOB 执行模型抽象为通用的有向无环图 DAG,可以将多 Stage 的任务串联或并行执行,调度引擎自动处理 Stage 的失败以及 Task 的失败。
7) 数据分片的高度弹性
  可以根据业务的特征,动态调整数据分片的个数,提升整体的应用执行效率。

  RDD 全称叫做弹性分布式数据集(Resilient Distributed Datasets),它是一种分布式的内存抽象,表示一个只读的记录分区的集合,它只能通过其他 RDD 转换而创建,为此,RDD 支持丰富的转换操作(如 map, join, filter, groupby 等),通过这种转换操作,新的 RDD 则包含了如何从其他 RDDs 衍生所必需的信息,所以说 RDDs 之间是有依赖关系的。基于 RDDs 之间的依赖,RDDs 会形成一个有向无环图 DAG,该 DAG 描述了整个流式计算的流程,实际执行的时候,RDD 是通过血缘关系(Lineage)一气呵成的,即使出现数据分区丢失,也可以通过血缘关系重建分区,总结起来,基于 RDD 的流式计算任务可描述为:从稳定的物理存储(如分布式文件系统)中加载记录,记录被传入由一组确定性操作构成的 DAG,然后写回稳定存储。另外 RDD 还可以将数据集缓存到内存中,使得在多个操作之间可以重用数据集,基于这个特点可以很方便地构建迭代型应用(图计算、机器学习等)或者交互式数据分析应用。可以说 Spark 最初也就是实现 RDD 的一个分布式系统,后面通过不断发展壮大成为现在较为完善的大数据生态系统,简单来讲,Spark-RDD 的关系类似于 Hadoop-MapReduce 关系。

1.4 RDD 特点

  RDD 表示只读的分区的数据集,对 RDD 进行改动,只能通过 RDD 的转换操作,由一个 RDD 得到一个新的 RDD,新的 RDD 包含了从其他 RDD 衍生所必需的信息。RDDs 之间存在依赖,RDD 的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化 RDD 来切断血缘关系。

1.4.1 分区

  RDD 逻辑上是分区的,每个分区的数据是抽象存在的,计算的时候会通过一个 compute 函数得到每个分区的数据。如果 RDD 是通过已有的文件系统构建,则 compute 函数是读取指定文件系统中的数据,如果 RDD 是通过其他 RDD 转换而来,则 compute 函数是执行转换逻辑将其他 RDD 的数据进行转换。
  [外链图片转存失败(img-lbW4grAz-1562861616812)(https://s2.ax1x.com/2019/04/26/EnWBm4.png)]

1.4.2 只读

如下图所示,RDD 是只读的,要想改变 RDD 中的数据,只能在现有的 RDD 基础上创建新的 RDD。
  
  由一个 RDD 转换到另一个 RDD,可以通过丰富的操作算子实现,不再像 MapReduce 那样只能写 map 和 reduce 了,如下图所示。
  [外链图片转存失败(img-9Gv7yI7o-1562861616812)(https://s2.ax1x.com/2019/04/26/EnWcfx.png)]
  RDD 的操作算子包括两类,一类叫做 transformations,它是用来将 RDD 进行转化,构建 RDD 的血缘关系;另一类叫做 actions,它是用来触发 RDD 的计算,得到 RDD 的相关计算结果或者将 RDD 保存的文件系统中。下图是 RDD 所支持的操作算子列表。
  [外链图片转存失败(img-iJvZyMOV-1562861616812)(https://s2.ax1x.com/2019/04/26/EnW4ne.png)]

1.4.3 依赖

  RDDs 通过操作算子进行转换,转换得到的新 RDD 包含了从其他 RDDs 衍生所必需的信息,RDDs 之间维护着这种血缘关系,也称之为依赖。如下图所示,依赖包括两种,一种是窄依赖,RDDs 之间分区是一一对应的,另一种是宽依赖,下游 RDD 的每个分区与上游 RDD (也称之为父 RDD)的每个分区都有关,是多对多的关系。
  
  通过 RDDs 之间的这种依赖关系,一个任务流可以描述为 DAG (有向无环图),如下图所示,在实际执行过程中宽依赖对应于 Shuffle (图中的 reduceByKey 和 join),窄依赖中的所有转换操作可以通过类似于管道的方式一气呵成执行(图中 map 和 union 可以一起执行)。
  

1.4.4 缓存

  如果在应用程序中多次使用同一个 RDD,可以将该 RDD 缓存起来,该 RDD 只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该 RDD 的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用。如下图所示,RDD-1 经过一系列的转换后得到 RDD-n 并保存到 hdfs,RDD-1 在这一过程中会有个中间结果,如果将其缓存到内存,那么在随后的 RDD-1 转换到 RDD-m 这一过程中,就不会计算其之前的 RDD-0 了。
  

1.4.5 CheckPoint

  虽然 RDD 的血缘关系天然地可以实现容错,当 RDD 的某个分区数据失败或丢失,可以通过血缘关系重建。但是对于长时间迭代型应用来说,随着迭代的进行,RDDs 之间的血缘关系会越来越长,一旦在后续迭代过程中出错,则需要通过非常长的血缘关系去重建,势必影响性能。为此,RDD 支持 checkpoint 将数据保存到持久化的存储中,这样就可以切断之前的血缘关系,因为 checkpoint 后的 RDD 不需要知道它的父 RDDs 了,它可以从 checkpoint 处拿到数据。
  给定一个 RDD 我们至少可以知道如下几点信息:
  1、分区数以及分区方式;
  2、由父 RDDs 衍生而来的相关依赖信息;
  3、计算每个分区的数据,计算步骤为:
    1)如果被缓存,则从缓存中取的分区的数据;
    2)如果被 checkpoint,则从 checkpoint 处恢复数据;
    3)根据血缘关系计算分区的数据。

第2章 RDD 编程

2.1 RDD 编程模型

  在 Spark 中,RDD 被表示为对象,通过对象上的方法调用来对 RDD 进行转换。经过一系列的 transformations 定义 RDD 之后,就可以调用 actions 触发 RDD 的计算,action 可以是向应用程序返回结果(count, collect 等),或者是向存储系统保存数据(saveAsTextFile 等)。在 Spark 中,只有遇到 action,才会执行 RDD 的计算(即延迟计算),这样在运行时可以通过管道的方式传输多个转换。
  要使用 Spark,开发者需要编写一个 Driver 程序,它被提交到集群以调度运行 Worker,如下图所示。Driver 中定义了一个或多个 RDD,并调用 RDD 上的 action,Worker 则执行 RDD 分区计算任务。
  [外链图片转存失败(img-ALJErinq-1562861616812)(https://s2.ax1x.com/2019/04/26/EnW50H.png)]
  Driver 和 Worker 内部示意图:
  

2.2 RDD 创建

  在 Spark 中创建 RDD 的创建方式大概可以分为三种:从集合中创建 RDD;从外部存储创建 RDD;从其他 RDD 创建。
  [外链图片转存失败(img-qyQlLhzn-1562861616814)(https://s2.ax1x.com/2019/04/26/EnWTAA.png)]

2.2.1、由一个已经存在的 Scala 集合创建,即集合并行化(测试用)
scala> val rdd1 = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8))

  Step1、而从集合中创建 RDD,Spark 主要提供了两种函数:parallelize 和 makeRDD。我们可以先看看这两个函数的声明:

def parallelize[T: ClassTag](
        seq: Seq[T],
        numSlices: Int = defaultParallelism): RDD[T]
 
def makeRDD[T: ClassTag](
        seq: Seq[T],
        numSlices: Int = defaultParallelism): RDD[T]
 
def makeRDD[T: ClassTag](
        seq: Seq[(T, Seq[String])]): RDD[T]

  Step2、我们可以从上面看出 makeRDD 有两种实现,而且第一个 makeRDD 函数接收的参数和 parallelize 完全一致。其实第一种 makeRDD 函数实现是依赖了 parallelize 函数的实现,来看看 Spark 中是怎么实现这个 makeRDD 函数的:

def makeRDD[T: ClassTag](
    seq: Seq[T],
    numSlices: Int = defaultParallelism): RDD[T] = withScope {
   
  parallelize(seq, numSlices)
}

  Step3、第一个 makeRDD 的实现可以自己指定分区的数量,它的默认分区取决于 CPU 的核心总数,示例代码如下:

scala> val rdd2 = sc.makeRDD(Array(1, 2, 3, 4, 5, 6))
rdd2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[1] at makeRDD at <console>:24
scala> rdd2.partitions.size
res1: Int = 4       本博主的 hadoop102 虚拟机 CPU 配置的是 4

  追踪默认分区的底层源码如下:

def parallelize[T: ClassTag](
    seq: Seq[T],
    numSlices: Int = defaultParallelism): RDD[T] = withScope {
   
  assertNotStopped()
  new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]())
}

def defaultParallelism: Int = {
   
    assertNotStopped()
    taskScheduler.defaultParallelism
}

def defaultParallelism(): Int

override def defaultParallelism(): Int = backend.defaultParallelism()

override def defaultParallelism(): Int = {
   
    conf.getInt("spark.default.parallelism", math.max(totalCoreCount.get(), 2))
}

  Step4、我们可以看出,这个 makeRDD 函数完全和 parallelize 函数一致。但是我们得看看第二种 makeRDD 函数的具体实现了,它接收的参数类型是 Seq[(T, Seq[String])],Spark 文档的说明是:

Distribute a local Scala collection to form an RDD, with one or more location preferences (hostnames of Spark nodes) for each object. Create a new partition for each collection item.

分发本地 Scala 集合以形成 RDD,每个对象具有一个或多个位置首选项(Spark 节点的主机名)。 为每个集合项创建一个新分区。

  原来,这个函数还为数据提供了位置信息,来看看我们怎么使用:

scala> val guigu1= sc.parallelize(List(1, 2, 3))
guigu1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[10] at parallelize at <console>:21
 
scala> val guigu2 = sc.makeRDD(List(1, 2, 3))
guigu2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[11] at makeRDD at <console>:21
 
scala> val seq = List((1, List("slave01")),| (2, List("slave02")))
seq: List[(Int, List[String])] = List((1, List(slave01)),
 (2,List(slave02)))
 
scala> val guigu3 = sc.makeRDD(seq)
guigu3: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[12] at makeRDD at <console>:23
 
scala> guigu3.preferredLocations(guigu3.partitions(1))
res26: Seq[String] = List(slave02)
 
scala> guigu3.preferredLocations(guigu3.partitions(0))
res27: Seq[String] = List(slave01)
 
scala> guigu1.preferredLocations(guigu1.partitions(0))
res28: Seq[String] = List()

  我们可以看到,makeRDD 函数有两种实现,第一种实现其实完全和 parallelize 一致;而第二种实现可以为数据提供位置信息,而除此之外的实现和 parallelize 函数也是一致的,如下:

def parallelize[T: ClassTag](seq: Seq[T], numSlices: Int = defaultParallelism): RDD[T]
 
def makeRDD[T: ClassTag](seq: Seq[(T, Seq[String])]): RDD[T]

  都是返回 ParallelCollectionRDD,而且这个 makeRDD 的实现不可以自己指定分区的数量,而是固定为 seq 参数的 size 大小。

  扩展知识:
RDD 的运行规划图

2.2.2、由外部存储系统的数据集创建(开发用)

包括本地的文件系统,还有所有 Hadoop 支持的数据集,比如 HDFS、Cassandra、HBase 等。

scala> val atguigu = sc.textFile("hdfs://hadoop102:9000/RELEASE")
atguigu: org.apache.spark.rdd.RDD[String] = hdfs://hadoop102:9000/RELEASE MapPartitionsRDD[4] at textFile at <console>:24

2.3 RDD 编程

  RDD 一般分为数值 RDD 和键值对 RDD,本章不进行具体区分,先统一来看,下一章会对键值对 RDD 做专门说明。

2.3.1 Transformation(转换)

  RDD 中的所有转换都是延迟加载的,也就是说,它们并不会直接计算结果。相反的,它们只是记住这些应用到基础数据集(例如一个文件)上的转换动作。只有当发生一个要求返回结果给 Driver
的动作时,这些转换才会真正运行。这种设计让 Spark 更加有效率地运行。

常用的 Transformation 如下

1、map(func)
返回一个新的 RDD,该 RDD 由每一个输入元素经过 func 函数转换后组成。
源码:

    def map[U: ClassTag](f: T => U): RDD[U]

示例代码:

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

scala> mapSource.collect()
res: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> val map = mapSource.map(_ * 2)
map: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[4] at map at <console>:26

scala> map.collect      或者    map.collect() 括号可以省略
res4: Array[Int] = Array(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

2、filter(func)
返回一个新的 RDD,该 RDD 由经过 func 函数计算后返回值为 true 的输入元素组成。
源码:

    def filter(f: T => Boolean): RDD[T] 

示例代码:

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

scala> filterSource.collect
res5: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> filterSource.filter(_ % 2 == 0).collect
res6: Array[Int] = Array(2, 4, 6, 8, 10)

3、 flatMap(func)
类似于 map,但是每一个输入元素可以被映射为 0 或多个输出元素(所以 func 应该返回一个序列,而不是单一元素)。
源码:

    def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]

示例代码:

scala> val flatMapSource = sc.makeRDD(Array("a b c", "d e f", "h i j"))
flatMapSource: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[2] at makeRDD at <console>:24

scala> flatMapSource.map(_.split(" ")).collect
res1: Array[Array[String]] = Array(Array(a, b, c), Array(d, e, f), Array(h, i, j))

scala> flatMapSource.map(_.split(" "))
res2: org.apache.spark.rdd.RDD[Array[String]] = MapPartitionsRDD[4] at map at <console>:27

scala> flatMapSource.flatMap(_.split(" ")).collect
res3: Array[String] = Array(a, b, c, d, e, f, h, i, j)

scala> flatMapSource.flatMap(_.split(" "))
res4: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[6] at flatMap at <console>:27

# flatMap(_.split(" ")) 等价于 flatMap(Array(_))

scala> flatMapSource.flatMap(Array(_)).collect
res5: Array[String] = Array(a b c, d e f, h i j)

scala> flatMapSource.collect
res6: Array[String] = Array(a b c, d e f, h i j)

4、mapPartitions(func)
类似于 map,但独立地在 RDD 的每一个分片上运行,因此在类型为 T 的 RDD 上运行时,func 的函数类型必须是 Iterator[T] => Iterator[U]。
假设有 N 个元素,有 M 个分区,那么 map 的函数的将被调用 N 次,而 mapPartitions 被调用 M 次,一个函数一次处理所有分区。mapPartitions 的执行效率要比 map 高。
源码:

    def mapPartitions[U: ClassTag](
        f: Iterator[T] => Iterator[U],
        preservesPartitioning: Boolean = false): RDD[U]

示例代码:

scala> val mapPartSource = sc.makeRDD(1 to 10, 5)
mapPartSource: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[8] at makeRDD at <console>:24

scala> mapPartSource.collect
res7: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> mapPartSource.mapPartitions(_.map(_ + "a"))
res8: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[9] at mapPartitions at <console>:27

scala> mapPartSource.mapPartitions(_.map(_ + "a")).collect
res9: Array[String] = Array(1a, 2a, 3a, 4a, 5a, 6a, 7a, 8a, 9a, 10a)

scala> mapPartSource.mapPartitions(items => items.map(x => x + "a")).collect
res10: Array[String] = Array(1a, 2a, 3a, 4a, 5a, 6a, 7a, 8a, 9a, 10a)

--------------------以下为扩展练习代码--------------------

scala> mapPartSource.partitions.size
res11: Int = 5

scala> mapPartSource.collect
res15: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> mapPartSource.mapPartitions(x => Iterator(x.mkString("|"))).collect      mkString() 该函数把集合元素转化为字符串,可能还会添加分隔符、前缀、后缀。
res16: Array[String] = Array(1|2, 3|4, 5|6, 7|8, 9|10)

scala> val mapPartSource2 = sc.makeRDD(Array("a b c", "d e f", "h i j"), 5)
mapPartSource2: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[13] at makeRDD at <console>:24

scala> mapPartSource2.mapPartitions(x => Iterator(x.mkString("|"))).collect
res17: Array[String] = Array("", a b c, "", d e f, h i j)

scala> val mapPartSource3 = sc.makeRDD(Array("a b c", "d e f", "h i j", "k l m", "o p q"), 2)
mapPartSource3: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[15] at makeRDD at <console>:24

scala> mapPartSource3.mapPartitions(x => Iterator(x.mkString("|"))).collect
res18: Array[String] = Array(a b c|d e f, h i j|k l m|o p q)

5、mapPartitionsWithIndex(func)
类似于 mapPartitions,但 func 带有一个整数参数表示分片的索引值,因此在类型为 T 的 RDD 上运行时,func 的函数类型必须是 (Int, Interator[T]) => Iterator[U]。
源码:

    private[spark] def mapPartitionsWithIndexInternal[U: ClassTag](
        f: (Int, Iterator[T]) => Iterator[U],
        preservesPartitioning: Boolean = false): RDD[U]

示例代码:

scala> val source = sc.makeRDD(1 to 10)
source: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at makeRDD at <console>:24

scala> source.partitions.size
res0: Int = 4

scala> source.mapPartitionsWithIndex((index, x) => Iterator(index + ":" + x.mkString("|"))).collect
res3: Array[String] = Array(0:1|2, 1:3|4|5, 2:6|7, 3:8|9|10)                    

6、sample(withReplacement, fraction, seed)
以指定的随机种子随机抽样出数量为 fraction 的数据,withReplacement 表示是抽出的数据是否放回,true 为有放回的抽样,false 为无放回的抽样,seed 用于指定随机数生成器种子。
例如:从 RDD 中随机且有放回的抽出 50% 的数据,随机种子值为 3(即可能以1 2 3的其中一个起始值)。主要用于观察大数据集的分布情况。
源码:

    def sample(
        withReplacement: Boolean,
        fraction: Double,
        seed: Long = Utils.random.nextLong): RDD[T]

示例代码:

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

scala> rdd.collect()
res11: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> var sample1 = rdd.sample(true, 0.4, 2).collect
sample1: Array[Int] = Array(1, 2, 2, 7, 7, 8, 9)    为什么抽样出7个数据呢?

scala> var sample2 = rdd.sample(false, 0.2, 3).collect
sample2: Array[Int] = Array(1, 9)

7、takeSample
和 sample 的区别是:takeSample 返回的是最终的结果集合。

8、union(otherDataset)
对源 RDD 和参数 RDD 求并集后返回一个新的 RDD。
源码:

    def union(other: RDD[T]): RDD[T]

示例代码:

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

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

scala> val rdd3 = rdd1.union(rdd2).collect
rdd3: Array[Int] = Array(1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10)

9、intersection(otherDataset)
对源 RDD 和参数 RDD 求交集后返回一个新的 RDD。
源码:

    def intersection(other: RDD[T]): RDD[T]

示例代码:

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

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

scala> val rdd3 = rdd1.intersection(rdd2).collect
rdd3: Array[Int] = Array(5, 6, 7) 

10、distinct([numTasks]))
对原 RDD 进行去重后返回一个新的 RDD。默认情况下,只有 8 个并行任务来操作,但是可以传入一个可选的 numTasks 参数改变它。
源码:

    def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
    
    def distinct(): RDD[T]

示例代码:

scala> val distinctRdd = sc.parallelize(List(1, 2, 1, 5, 2, 9, 6, 1))
distinctRdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[24] at parallelize at <console>:24

scala> distinctRdd.distinct().collect
res12: Array[Int] = Array(1, 9, 5, 6, 2)

scala> distinctRdd.distinct(2).collect
res13: Array[Int] = Array(6, 2, 1, 9, 5)

11、partitionBy
对 KV 结构 RDD 进行重新分区,如果原有的 partionRDD 和现有的 partionRDD 是一致的话就不进行分区,否则会生成 shuffleRDD。
源码:

    def partitionBy(partitioner: Partitioner): RDD[(K, V)]

示例代码:

scala> val rdd = sc.parallelize(Array((1,"aaa"), (2,"bbb"), (3,"ccc"), (4,"ddd")), 2)
rdd: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[31] at parallelize at <console>:24

scala> rdd.partitions.size
res14: Int = 2

scala> var rdd2 = rdd.partitionBy(new org.apache.spark.HashPartitioner(3))
rdd2: org.apache.spark.rdd.RDD[(Int, String)] = ShuffledRDD[32] at partitionBy at <console>:26

scala> rdd2.partitions.size
res15: Int = 3

12、reduceByKey(func, [numTasks])
在一个 (K,V) 的 RDD 上调用,返回一个 (K,V) 的 RDD,使用指定的 reduce 函数,将相同 key 的值聚合到一起,reduce 任务的个数可以通过第二个可选的参数来设置。
源码:

    def reduceByKey(partitioner: Partitioner, func: (V, V) => V): RDD[(K, V)]
    
    def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]
    
    def reduceByKey(func: (V, V) => V): RDD[(K, V)]

示例代码:

scala> val rdd = sc.parallelize(List(("female",1), ("male",5), ("female",5), ("male",2)))
rdd: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[33] at parallelize at <console>:24

scala> val reduce = rdd.reduceByKey((x, y) => x + y)
reduce: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[34] at reduceByKey at <console>:26

scala> reduce.collect()
res16: Array[(String, Int)] = Array((female,6), (male,7))

--------------------以下为扩展练习代码--------------------

scala> val rdd = sc.makeRDD(1 to 15).union(sc.makeRDD(7 to 20))
rdd: org.apache.spark.rdd.RDD[Int] = UnionRDD[2] at union at <console>:24

scala> rdd.map(x => (x, 1))
res0: org.apache.spark.rdd.RDD[(Int, Int)] = MapPartitionsRDD[3] at map at <console>:27

scala> rdd.map((_, 1))
res2: org.apache.spark.rdd.RDD[(Int, Int)] = MapPartitionsRDD[4] at map at <console>:27

scala> res0.collect
res3: Array[(Int, Int)] = Array((1,1), (2,1), (3,1), (4,1),
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值