RDD讲解:入门到深入

从案例分析

在这份 WordCount 代码中, 大致的思路如下:

1.使用 sc.textFile() 方法读取 HDFS 中的文件, 并生成一个 RDD

2.使用 flatMap 算子将读取到的每一行字符串打散成单词, 并把每个单词变成新的行

3.使用 map 算子将每个单词转换成 (word, 1) 这种元组形式

4.使用 reduceByKey 统计单词对应的频率

其中所使用到的算子有如下几个:

  • flatMap 是一对多
  • map 是一对一
  • reduceByKey 是按照 Key 聚合, 类似 MapReduce 中的 Shuffled

RDD讲解:入门到深入_第1张图片

RDD是什么

RDD讲解:入门到深入_第2张图片

定义

RDD, 全称为 Resilient Distributed Datasets, 是一个容错的, 并行的数据结构, 可以让用户显式地将数据存储到磁盘和内存中, 并能控制数据的分区.

同时, RDD 还提供了一组丰富的操作来操作这些数据. 在这些操作中, 诸如 map, flatMap, filter 等转换操作实现了 Monad 模式, 很好地契合了 Scala 的集合操作. 除此之外, RDD 还提供了诸如 join, groupBy, reduceByKey 等更为方便的操作, 以支持常见的数据运算.

通常来讲, 针对数据处理有几种常见模型, 包括: Iterative Algorithms, Relational Queries, MapReduce, Stream Processing. 例如 Hadoop MapReduce 采用了 MapReduce 模型, Storm 则采用了 Stream Processing 模型. RDD 混合了这四种模型, 使得 Spark 可以应用于各种大数据处理场景.

RDD 作为数据结构, 本质上是一个只读的分区记录集合. 一个 RDD 可以包含多个分区, 每个分区就是一个 DataSet 片段.

RDD 之间可以相互依赖, 如果 RDD 的每个分区最多只能被一个子 RDD 的一个分区使用,则称之为窄依赖, 若被多个子 RDD 的分区依赖,则称之为宽依赖. 不同的操作依据其特性, 可能会产生不同的依赖. 例如 map 操作会产生窄依赖, 而 join 操作则产生宽依赖.

特点:

  1. RDD 是一个编程模型

    a. RDD 允许用户显式的指定数据存放在内存或者磁盘

    b.RDD 是分布式的, 用户可以控制 RDD 的分区

  2. RDD 是一个编程模型

    a.RDD 提供了丰富的操作

    b.RDD 提供了 map, flatMap, filter 等操作符, 用以实现 Monad 模式

    c.RDD 提供了 reduceByKey, groupByKey 等操作符, 用以操作 Key-Value 型数据

    d.RDD 提供了 max, min, mean 等操作符, 用以操作数字型的数据

  3. RDD 是混合型的编程模型, 可以支持迭代计算, 关系查询, MapReduce, 流计算

  4. RDD 是只读的

  5. RDD 之间有依赖关系, 根据执行操作的操作符的不同, 依赖关系可以分为宽依赖和窄依赖

RDD 的分区

RDD讲解:入门到深入_第3张图片

整个 WordCount 案例的程序从结构上可以用上图表示, 分为两个大部分

存储

文件如果存放在 HDFS 上, 是分块的, 类似上图所示, 这个 wordcount.txt 分了三块

计算

Spark 不止可以读取 HDFS, Spark 还可以读取很多其它的数据集, Spark 可以从数据集中创建出 RDD

例如上图中, 使用了一个 RDD 表示 HDFS 上的某一个文件, 这个文件在 HDFS 中是分三块, 那么 RDD 在读取的时候就也有三个分区, 每个 RDD 的分区对应了一个 HDFS 的分块

后续 RDD 在计算的时候, 可以更改分区, 也可以保持三个分区, 每个分区之间有依赖关系, 例如说 RDD2 的分区一依赖了 RDD1 的分区一

RDD 之所以要设计为有分区的, 是因为要进行分布式计算, 每个不同的分区可以在不同的线程, 或者进程, 甚至节点中, 从而做到并行计算

  1. RDD 是弹性分布式数据集

  2. RDD 一个非常重要的前提和基础是 RDD 运行在分布式环境下, 其可以分区

简略的说, RDD 有三种创建方式

  • RDD 可以通过本地集合直接创建

  • RDD 也可以通过读取外部数据集来创建

  • RDD 也可以通过其它的 RDD 衍生而来

通过本地集合直接创建 RDD

val conf = new SparkConf().setMaster("local[2]")
val sc = new SparkContext(conf)

val list = List(1, 2, 3, 4, 5, 6)
val rddParallelize = sc.parallelize(list, 2)
val rddMake = sc.makeRDD(list, 2)

通过 parallelize 和 makeRDD 这两个 API 可以通过本地集合创建 RDD

这两个 API 本质上是一样的, 在 makeRDD 这个方法的内部, 最终也是调用了 parallelize

因为不是从外部直接读取数据集的, 所以没有外部的分区可以借鉴, 于是在这两个方法都都有两个参数, 第一个参数是本地集合, 第二个参数是分区数

通过读取外部文件创建 RDD

val conf = new SparkConf().setMaster("local[2]")
val sc = new SparkContext(conf)

val source: RDD[String] = sc.textFile("hdfs://node01:8020/dataset/wordcount.txt")
访问方式:
  • 支持访问文件夹, 例如 sc.textFile(“hdfs:///dataset”)

  • 支持访问压缩文件, 例如 sc.textFile(“hdfs:///dataset/words.gz”)

  • 支持通过通配符访问, 例如 sc.textFile(“hdfs:///dataset/*.txt”)

如果把 Spark 应用跑在集群上, 则 Worker 有可能在任何一个节点运行

所以如果使用 file:///…​; 形式访问本地文件的话, 要确保所有的 Worker 中对应路径上有这个文件, 否则可能会报错无法找到文件

分区
  • 默认情况下读取 HDFS 中文件的时候, 每个 HDFS 的 block 对应一个 RDD 的 partition, block 的默认是128M

  • 通过第二个参数, 可以指定分区数量, 例如 sc.textFile(“hdfs://node01:8020/dataset/wordcount.txt”, 20)

  • 如果通过第二个参数指定了分区, 这个分区数量一定不能小于block

通常每个 CPU core 对应 2 - 4 个分区是合理的值

支持的平台
  • 支持 Hadoop 的几乎所有数据格式, 支持 HDFS 的访问
  • 通过第三方的支持, 可以访问AWS和阿里云中的文件, 详情查看对应平台的 API

通过其它的 RDD 衍生新的 RDD

val conf = new SparkConf().setMaster("local[2]")
val sc = new SparkContext(conf)

val source: RDD[String] = sc.textFile("hdfs://node01:8020/dataset/wordcount.txt", 20)
val words = source.flatMap { line => line.split(" ") }
  • source 是通过读取 HDFS 中的文件所创建的

  • words 是通过 source 调用算子 map 生成的新 RDD

总结:RDD 的可以通过三种方式创建, 通过本地集合创建, 通过外部数据集创建, 通过其它的 RDD 衍生

RDD 算子

Map 算子

sc.parallelize(Seq(1, 2, 3))
  .map( num => num * 10 )
  .collect()

RDD讲解:入门到深入_第4张图片

RDD讲解:入门到深入_第5张图片

作用

把 RDD 中的数据 一对一 的转为另一种形式

调用

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

参数

f → Map 算子是 原RDD → 新RDD 的过程, 这个函数的参数是原 RDD 数据, 返回值是经过函数转换的新 RDD 的数据

注意点

Map 是一对一, 如果函数是 String → Array[String] 则新的 RDD 中每条数据就是一个数组

FlatMap 算子

sc.parallelize(Seq("Hello lily", "Hello lucy", "Hello tim"))
  .flatMap( line => line.split(" ") )
  .collect()

RDD讲解:入门到深入_第6张图片
RDD讲解:入门到深入_第7张图片

作用

FlatMap 算子和 Map 算子类似, 但是 FlatMap 是一对多

调用

def flatMap[U: ClassTag](f: T ⇒ List[U]): RDD[U]

参数

f → 参数是原 RDD 数据, 返回值是经过函数转换的新 RDD 的数据, 需要注意的是返回值是一个集合, 集合中的数据会被展平后再放入新的 RDD

注意点

flatMap 其实是两个操作, 是 map + flatten, 也就是先转换, 后把转换而来的 List 展开

ReduceByKey 算子

sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
  .reduceByKey( (curr, agg) => curr + agg )
  .collect()

RDD讲解:入门到深入_第8张图片
RDD讲解:入门到深入_第9张图片

作用

首先按照 Key 分组, 接下来把整组的 Value 计算出一个聚合值, 这个操作非常类似于 MapReduce 中的 Reduce

调用

def reduceByKey(func: (V, V) ⇒ V): RDD[(K, V)]

参数

func → 执行数据处理的函数, 传入两个参数, 一个是当前值, 一个是局部汇总, 这个函数需要有一个输出, 输出就是这个 Key 的汇总结果

注意点

  • ReduceByKey 只能作用于 Key-Value 型数据, Key-Value 型数据在当前语境中特指 Tuple2

  • ReduceByKey 是一个需要 Shuffled 的操作

  • 和其它的 Shuffled 相比, ReduceByKey是高效的, 因为类似 MapReduce 的, 在 Map 端有一个 Cominer, 这样 I/O 的数据便会减少

深入 RDD

案例

需求:

给定一个网站的访问记录, 俗称 Access log
计算其中出现的独立 IP, 以及其访问的次数

val config = new SparkConf().setAppName("ip_ana").setMaster("local[6]")
val sc = new SparkContext(config)

val result = sc.textFile("dataset/access_log_sample.txt")
  .map(item => (item.split(" ")(0), 1))
  .filter(item => StringUtils.isNotBlank(item._1))
  .reduceByKey((curr, agg) => curr + agg)
  .sortBy(item => item._2, false)
  .take(10)

result.foreach(item => println(item))

1.假设要针对整个网站的历史数据进行处理, 量有 1T, 如何处理?

放在集群中, 利用集群多台计算机来并行处理

2.如何放在集群中运行?
RDD讲解:入门到深入_第10张图片
简单来讲, 并行计算就是同时使用多个计算资源解决一个问题, 有如下四个要点

  • 要解决的问题必须可以分解为多个可以并发计算的部分

  • 每个部分要可以在不同处理器上被同时执行

  • 需要一个共享内存的机制

  • 需要一个总体上的协作机制来进行调度

3.如果放在集群中的话, 可能要对整个计算任务进行分解, 如何分解?
RDD讲解:入门到深入_第11张图片

概述

对于 HDFS 中的文件, 是分为不同的 Block 的 
在进行计算的时候, 就可以按照 Block 来划分, 每一个 Block 对应一个不同的计算单元

扩展

RDD 并没有真实的存放数据, 数据是从 HDFS 中读取的, 在计算的过程中读取即可 
RDD 至少是需要可以 分片 的, 因为HDFS中的文件就是分片的, RDD 分片的意义在于表示对源数据集每个分片的计算, RDD 可以分片也意味着 可以并行计算

4.移动数据不如移动计算是一个基础的优化, 如何做到?
RDD讲解:入门到深入_第12张图片

5.在集群中运行, 需要很多节点之间配合, 出错的概率也更高, 出错了怎么办?
RDD讲解:入门到深入_第13张图片

RDD讲解:入门到深入_第14张图片

6.假如任务特别复杂, 流程特别长, 有很多 RDD 之间有依赖关系, 如何优化?
RDD讲解:入门到深入_第15张图片
上面提到了可以使用依赖关系来进行容错, 但是如果依赖关系特别长的时候, 这种方式其实也比较低效, 这个时候就应该使用另外一种方式, 也就是记录数据集的状态

在 Spark 中有两个手段可以做到
缓存

Checkpoint

再谈 RDD

RDD 为什么会出现?

在 RDD 出现之前, 当时 MapReduce 是比较主流的, 而 MapReduce 如何执行迭代计算的任务呢?
在这里插入图片描述

多个 MapReduce 任务之间没有基于内存的数据共享方式, 只能通过磁盘来进行共享

这种方式明显比较低效

RDD 如何解决迭代计算非常低效的问题呢?
在这里插入图片描述

在 Spark 中, 其实最终 Job3 从逻辑上的计算过程是: Job3 = (Job1.map).filter, 整个过程是共享内存的, 而不需要将中间结果存放在可靠的分布式文件系统中

这种方式可以在保证容错的前提下, 提供更多的灵活, 更快的执行速度, RDD 在执行迭代型任务时候的表现可以通过下面代码体现

// 线性回归
val points = sc.textFile(...)
	.map(...)
	.persist(...)
val w = randomValue
for (i <- 1 to 10000) {
    val gradient = points.map(p => p.x * (1 / (1 + exp(-p.y * (w dot p.x))) - 1) * p.y)
    	.reduce(_ + _)
    w -= gradient
}

在这个例子中, 进行了大致 10000 次迭代, 如果在 MapReduce 中实现, 可能需要运行很多 Job, 每个 Job 之间都要通过 HDFS 共享结果, 熟快熟慢一窥便知

RDD 的特点

RDD 不仅是数据集, 也是编程模型

RDD 即是一种数据结构, 同时也提供了上层 API, 同时 RDD 的 API 和 Scala 中对集合运算的 API 非常类似, 同样也都是各种算子
RDD讲解:入门到深入_第16张图片

RDD 的算子大致分为两类:

  • Transformation 转换操作, 例如 map flatMap filter 等

  • Action 动作操作, 例如 reduce collect show 等

执行 RDD 的时候, 在执行到转换操作的时候, 并不会立刻执行, 直到遇见了 Action 操作, 才会触发真正的执行, 这个特点叫做 惰性求值

RDD 可以分区

RDD讲解:入门到深入_第17张图片
RDD 是一个分布式计算框架, 所以, 一定是要能够进行分区计算的, 只有分区了, 才能利用集群的并行计算能力

同时, RDD 不需要始终被具体化, 也就是说: RDD 中可以没有数据, 只要有足够的信息知道自己是从谁计算得来的就可以, 这是一种非常高效的容错方式

RDD 是只读的

RDD讲解:入门到深入_第18张图片
RDD 是只读的, 不允许任何形式的修改. 虽说不能因为 RDD 和 HDFS 是只读的, 就认为分布式存储系统必须设计为只读的. 但是设计为只读的, 会显著降低问题的复杂度, 因为 RDD 需要可以容错, 可以惰性求值, 可以移动计算, 所以很难支持修改.

RDD2 中可能没有数据, 只是保留了依赖关系和计算函数, 那修改啥?

如果因为支持修改, 而必须保存数据的话, 怎么容错?

如果允许修改, 如何定位要修改的那一行? RDD 的转换是粗粒度的, 也就是说, RDD 并不感知具体每一行在哪.

RDD 是可以容错的

RDD讲解:入门到深入_第19张图片

RDD 的容错有两种方式

  • 保存 RDD 之间的依赖关系, 以及计算函数, 出现错误重新计算

  • 直接将 RDD 的数据存放在外部存储系统, 出现错误直接读取, Checkpoint

什么叫做弹性分布式数据集

分布式

RDD 支持分区, 可以运行在集群中

弹性

  • RDD 支持高效的容错

  • RDD 中的数据即可以缓存在内存中, 也可以缓存在磁盘中, 也可以缓存在外部存储中

数据集

  • RDD 可以不保存具体数据, 只保留创建自己的必备信息, 例如依赖和计算函数

  • RDD 也可以缓存起来, 相当于存储具体数据

总结: RDD 的五大属性

首先整理一下上面所提到的 RDD 所要实现的功能:

1. RDD 有分区

2. RDD 要可以通过依赖关系和计算函数进行容错

3. RDD 要针对数据本地性进行优化

4.RDD 支持 MapReduce 形式的计算, 所以要能够对数据进行 Shuffled

对于 RDD 来说, 其中应该有什么内容呢? 如果站在 RDD 设计者的角度上, 这个类中, 至少需要什么属性?

* Partition List 分片列表, 记录 RDD 的分片, 可以在创建 RDD 的时候指定分区数目, 也可以通过算子来生成新的 RDD 从而改变分区数目

* Compute Function 为了实现容错, 需要记录 RDD 之间转换所执行的计算函数

* RDD Dependencies RDD 之间的依赖关系, 要在 RDD 中记录其上级 RDD 是谁, 从而实现容错和计算

* Partitioner 为了执行 Shuffled 操作, 必须要有一个函数用来计算数据应该发往哪个分区

* Preferred Location 优先位置, 为了实现数据本地性操作, 从而移动计算而不是移动存储, 需要记录每个 RDD 分区最好应该放置在什么位置

RDD 的算子

分类

RDD 中的算子从功能上分为两大类

1.Transformation(转换) 它会在一个已经存在的 RDD 上创建一个新的 RDD, 将旧的 RDD 的数据转换为另外一种形式后放入新的 RDD

2.Action(动作) 执行各个分区的计算任务, 将的到的结果返回到 Driver 中

RDD 中可以存放各种类型的数据, 那么对于不同类型的数据, RDD 又可以分为三类

1.针对基础类型(例如 String)处理的普通算子

2.针对 Key-Value 数据处理的 byKey 算子

3.针对数字类型数据处理的计算算子

特点

  • Spark 中所有的 Transformations 是 Lazy(惰性) 的, 它们不会立即执行获得结果. 相反, 它们只会记录在数据集上要应用的操作. 只有当需要返回结果给 Driver 时, 才会执行这些操作, 通过 DAGScheduler 和 TaskScheduler 分发到集群中运行, 这个特性叫做 惰性求值

  • 默认情况下, 每一个 Action 运行的时候, 其所关联的所有 Transformation RDD 都会重新计算, 但是也可以使用 presist 方法将 RDD 持久化到磁盘或者内存中. 这个时候为了下次可以更快的访问, 会把数据保存到集群上.

Transformations 算子

map(T ⇒ U)

sc.parallelize(Seq(1, 2, 3))
  .map( num => num * 10 )
  .collect()

RDD讲解:入门到深入_第20张图片

flatMap(T ⇒ List[U])

sc.parallelize(Seq("Hello lily", "Hello lucy", "Hello tim"))
  .flatMap( line => line.split(" ") )
  .collect()

RDD讲解:入门到深入_第21张图片

filter(T ⇒ Boolean)

sc.parallelize(Seq(1, 2, 3))
  .filter( value => value >= 3 )
  .collect()

RDD讲解:入门到深入_第22张图片

mapPartitions(List[T] ⇒ List[U])

RDD[T] ⇒ RDD[U] 和 map 类似, 但是针对整个分区的数据转换

mapPartitionsWithIndex

和 mapPartitions 类似, 只是在函数中增加了分区的 Index

mapValues

sc.parallelize(Seq(("a", 1), ("b", 2), ("c", 3)))
  .mapValues( value => value * 10 )
  .collect()

RDD讲解:入门到深入_第23张图片

sample(withReplacement, fraction, seed)

sc.parallelize(Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
  .sample(withReplacement = true, 0.6, 2)
  .collect()

RDD讲解:入门到深入_第24张图片

union(other)

val rdd1 = sc.parallelize(Seq(1, 2, 3))
val rdd2 = sc.parallelize(Seq(4, 5, 6))
rdd1.union(rdd2)
  .collect()

RDD讲解:入门到深入_第25张图片

intersection(other)

val rdd1 = sc.parallelize(Seq(1, 2, 3, 4, 5))
val rdd2 = sc.parallelize(Seq(4, 5, 6, 7, 8))
rdd1.intersection(rdd2)
  .collect()

RDD讲解:入门到深入_第26张图片

subtract(other, numPartitions)

(RDD[T], RDD[T]) ⇒ RDD[T] 差集, 可以设置分区数

distinct(numPartitions)

sc.parallelize(Seq(1, 1, 2, 2, 3))
  .distinct()
  .collect()

RDD讲解:入门到深入_第27张图片

reduceByKey((V, V) ⇒ V, numPartition)

sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
  .reduceByKey( (curr, agg) => curr + agg )
  .collect()

RDD讲解:入门到深入_第28张图片
RDD讲解:入门到深入_第29张图片
RDD讲解:入门到深入_第30张图片

groupByKey()

sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
  .groupByKey()
  .collect()

RDD讲解:入门到深入_第31张图片

combineByKey()

val rdd = sc.parallelize(Seq(
  ("zhangsan", 99.0),
  ("zhangsan", 96.0),
  ("lisi", 97.0),
  ("lisi", 98.0),
  ("zhangsan", 97.0))
)

val combineRdd = rdd.combineByKey(
  score => (score, 1),
  (scoreCount: (Double, Int),newScore) => (scoreCount._1 + newScore, scoreCount._2 + 1),
  (scoreCount1: (Double, Int), scoreCount2: (Double, Int)) =>
    (scoreCount1._1 + scoreCount2._1, scoreCount1._2 + scoreCount2._2)
)

val meanRdd = combineRdd.map(score => (score._1, score._2._1 / score._2._2))

meanRdd.collect()

RDD讲解:入门到深入_第32张图片
RDD讲解:入门到深入_第33张图片

aggregateByKey()

val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.aggregateByKey(0.8)(
  seqOp = (zero, price) => price * zero,
  combOp = (curr, agg) => curr + agg
).collect()
println(result)

RDD讲解:入门到深入_第34张图片
RDD讲解:入门到深入_第35张图片

foldByKey(zeroValue)((V, V) ⇒ V)

sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
  .foldByKey(zeroValue = 10)( (curr, agg) => curr + agg )
  .collect()

在这里插入图片描述
RDD讲解:入门到深入_第36张图片
RDD讲解:入门到深入_第37张图片

join(other, numPartitions)

val rdd1 = sc.parallelize(Seq(("a", 1), ("a", 2), ("b", 1)))
val rdd2 = sc.parallelize(Seq(("a", 10), ("a", 11), ("a", 12)))

rdd1.join(rdd2).collect()

RDD讲解:入门到深入_第38张图片
RDD讲解:入门到深入_第39张图片

cogroup(other, numPartitions)

val rdd1 = sc.parallelize(Seq(("a", 1), ("a", 2), ("a", 5), ("b", 2), ("b", 6), ("c", 3), ("d", 2)))
val rdd2 = sc.parallelize(Seq(("a", 10), ("b", 1), ("d", 3)))
val rdd3 = sc.parallelize(Seq(("b", 10), ("a", 1)))

val result1 = rdd1.cogroup(rdd2).collect()
val result2 = rdd1.cogroup(rdd2, rdd3).collect()

/*
执行结果:
Array(
  (d,(CompactBuffer(2),CompactBuffer(3))),
  (a,(CompactBuffer(1, 2, 5),CompactBuffer(10))),
  (b,(CompactBuffer(2, 6),CompactBuffer(1))),
  (c,(CompactBuffer(3),CompactBuffer()))
)
 */
println(result1)

/*
执行结果:
Array(
  (d,(CompactBuffer(2),CompactBuffer(3),CompactBuffer())),
  (a,(CompactBuffer(1, 2, 5),CompactBuffer(10),CompactBuffer(1))),
  (b,(CompactBuffer(2, 6),CompactBuffer(1),Co...
 */
println(result2)

RDD讲解:入门到深入_第40张图片
RDD讲解:入门到深入_第41张图片

cartesian(other)

(RDD[T], RDD[U]) ⇒ RDD[(T, U)] 生成两个 RDD 的笛卡尔积

sortBy(ascending, numPartitions)

val rdd1 = sc.parallelize(Seq(("a", 3), ("b", 2), ("c", 1)))
val sortByResult = rdd1.sortBy( item => item._2 ).collect()
val sortByKeyResult = rdd1.sortByKey().collect()

println(sortByResult)
println(sortByKeyResult)

RDD讲解:入门到深入_第42张图片

partitionBy(partitioner)

使用用传入的 partitioner 重新分区, 如果和当前分区函数相同, 则忽略操作

coalesce(numPartitions)

减少分区数

val rdd = sc.parallelize(Seq(("a", 3), ("b", 2), ("c", 1)))
val oldNum = rdd.partitions.length

val coalesceRdd = rdd.coalesce(4, shuffle = true)
val coalesceNum = coalesceRdd.partitions.length

val repartitionRdd = rdd.repartition(4)
val repartitionNum = repartitionRdd.partitions.length

print(oldNum, coalesceNum, repartitionNum)

RDD讲解:入门到深入_第43张图片

repartition(numPartitions)

重新分区

repartitionAndSortWithinPartitions

重新分区的同时升序排序, 在partitioner中排序, 比先重分区再排序要效率高, 建议使用在需要分区后再排序的场景使用

Action 算子

reduce( (T, T) ⇒ U )

val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.reduce((curr, agg) => ("总价", curr._2 + agg._2))
println(result)

RDD讲解:入门到深入_第44张图片

collect()

以数组的形式返回数据集中所有元素

count()

返回元素个数

first()

返回第一个元素

take( N )

返回前 N 个元素

takeSample(withReplacement, fract)

类似于 sample, 区别在这是一个Action, 直接返回结果

fold(zeroValue)( (T, T) ⇒ U )

指定初始值和计算函数, 折叠聚合整个数据集

saveAsTextFile(path)

将结果存入 path 对应的文件中

saveAsSequenceFile(path)

将结果存入 path 对应的 Sequence 文件中

countByKey()

val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.countByKey()
println(result)

RDD讲解:入门到深入_第45张图片

foreach( T ⇒ …​ )

遍历每一个元素

应用

```scala
val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
// 结果: Array((手机,10.0), (手机,15.0), (电脑,20.0))
println(rdd.collect())
// 结果: Array((手机,10.0), (手机,15.0))
println(rdd.take(2))
// 结果: (手机,10.0)
println(rdd.first())
总结
RDD 的算子大部分都会生成一些专用的 RDD
* map, flatMap, filter 等算子会生成 MapPartitionsRDD

* coalesce, repartition 等算子会生成 CoalescedRDD

常见的 RDD 有两种类型
* 转换型的 RDD, Transformation

* 动作型的 RDD, Action

常见的 Transformation 类型的 RDD
* map

* flatMap

* filter

* groupBy

* reduceByKey

常见的 Action 类型的 RDD
* collect

* countByKey

* reduce

RDD 对不同类型数据的支持

一般情况下 RDD 要处理的数据有三类

  • 字符串

  • 键值对

  • 数字型

RDD 的算子设计对这三类不同的数据分别都有支持

  • 对于以字符串为代表的基本数据类型是比较基础的一些的操作, 诸如 map, flatMap, filter 等基础的算子

  • 对于键值对类型的数据, 有额外的支持, 诸如 reduceByKey, groupByKey 等 byKey 的算子

  • 同样对于数字型的数据也有额外的支持, 诸如 max, min 等

RDD 对键值对数据的额外支持

键值型数据本质上就是一个二元元组, 键值对类型的 RDD 表示为 RDD[(K, V)]

RDD 对键值对的额外支持是通过隐式支持来完成的, 一个 RDD[(K, V)], 可以被隐式转换为一个 PairRDDFunctions 对象, 从而调用其中的方法.

在这里插入图片描述

既然对键值对的支持是通过 PairRDDFunctions 提供的, 那么从 PairRDDFunctions 中就可以看到这些支持有什么

类别 算子

聚合操作

reduceByKey

foldByKey

combineByKey

分组操作

cogroup

groupByKey

连接操作

join

leftOuterJoin

rightOuterJoin

排序操作

sortBy

sortByKey

Action

countByKey

take

collect

RDD 对数字型数据的额外支持

对于数字型数据的额外支持基本上都是 Action 操作, 而不是转换操作

算子 含义

count

个数

mean

均值

sum

求和

max

最大值

min

最小值

variance

方差

sampleVariance

从采样中计算方差

stdev

标准差

sampleStdev

采样的标准差

val rdd = sc.parallelize(Seq(1, 2, 3))
// 结果: 3
println(rdd.max())

你可能感兴趣的:(Spark)