Spark之RDD(弹性分布式数据集) 详解

1.RDD简述

RDD简单的解释:
RDD是将数据项拆分为多个分区的集合,存储在集群的工作节点上的内存和磁盘中,并执行正确的操作。
复杂的解释:
RDD是用于数据转换的接口,如map算子、filter算子;
RDD指向了存储在HDFS、Cassandra、 HBase等、 或缓存
(内存、内存+磁盘、仅磁盘等),或在故障或缓存收回时重新计算其他RDD分区中的数据

RDD(Resilient Distributed Dataset):弹性分布式数据集,Spark计算的基石,为用户屏蔽了底层对数据的复杂抽象和处理,为用户提供了一组方便的数据转换与求值方法。

Spark 的核心是建立在统一的抽象 RDD 之上,基于 RDD 的转换和行动操作使得 Spark 的各个组件可以无缝进行集成,从而在同一个应用程序中完成大数据计算任务。

RDD 提供了一个抽象的数据架构,从而让开发者不必担心底层数据的分布式特性,只需将具体的应用逻辑表达为一系列转换处理,不同 RDD 之间的转换操作形成依赖关系,可以实现管道化,从而避免了中间结果的存储,大大降低了数据复制、磁盘 IO 和序列化开销。

2.RDD特点

  1. 分布式( Distributed)
    数据的计算并非只局限于单个节点,而是多个节点之间协同计算得到
  2. 分区
    RDD是分区的,RDD里面的具体数据是分布在多台机器上的Executor里面的。堆内内存和堆外内存+磁盘。
  3. 不可变
    RDD是不可变的,如果需要在一个RDD上进行转换操作,则会生成一个新的RDD
  4. 数据集
    RDD是只读的、分区记录的集合,每个分区分布在集群的不同节点上;
    RDD并不存储真正的数据,只是对数据和操作的描述
  5. 弹性(Resilient)
    自动进行存储方式的切换 RDD优先存放在内存中,当内存不足,Spark自动将RDD写入磁盘
  6. 容错性
    根据数据血统,可以自动从节点失败中恢复分区;
    基于Linage的高效容错机制,在任何时候都能进行重算,根据数据血统,可以自动从节点失败中恢复分区,各个分片之间的数据互不影响
  7. 计算
    计算是分层的,有应用->Job->Stage->TaskSet->Task,每一层都有对应的计算的保障与重复机制,保障计算不会由于一些突发因素而终止。
  8. 分片
    可以根据业务需求或者一些算子来重新调整RDD中的数据分布。

3.RDD运行原理

一个 RDD 就是一个分布式对象集合,提供了一种高度受限的共享内存模型,其本质上是一个只读的分区记录集合,不能直接修改。
每个 RDD 可以分成多个分区,每个分区就是一个数据集片段,并且一个 RDD 的不同分区可以保存到集群中不同的节点上,从而可以在集群中的不同节点上进行并行计算。

RDD 提供了一组丰富的操作以支持常见的数据运算,分为“行动”(Action)和“转换”(Transformation)两种类型,前者用于执行计算并指定输出的形式,后者指定 RDD 之间的相互依赖关系。

RDD 提供的转换接口都非常简单,都是类似 map 、filter 、groupBy 、join 等粗粒度的数据转换操作,而不是针对某个数据项的细粒度修改。因此,RDD 比较适合对于数据集中元素执行相同操作的批处理式应用,而不适合用于需要异步、细粒度状态的应用,比如 Web 应用系统、增量式的网页爬虫等。

RDD 的执行过程:

  1. 读入外部的数据源(或者内存中的集合)进行 RDD 创建;
  2. RDD 经过一系列的 “转换” 操作,每一次都会产生不同的 RDD,供给下一个转换使用;
  3. 最后一个 RDD 经过 “行动” 操作进行处理,并输出指定的数据类型和值。

总结:
RDD 采用了惰性调用,即在 RDD 的执行过程中,所有的转换操作都不会执行真正的操作,只会记录依赖关系,而只有遇到了行动操作,才会触发真正的计算,并根据之前的依赖关系得到最终的结果。
如下图所示来举例说明RDD的执行过程:开始从输入中创建了两个 RDD,分别是 A 和 C,然后经过一系列的转换操作,最终生成了一个 F,这也是一个 RDD。

注意,这些转换操作的执行过程中并没有执行真正的计算,基于创建的过程也没有执行真正的计算,而只是记录的数据流向轨迹。
当 F 执行了行为操作并生成输出数据时,Spark 才会根据 RDD 的依赖关系生成有向无环图(DAG),并从起点开始执行真正的计算。
正是 RDD 的这种惰性调用机制,使得转换操作得到的中间结果不需要保存,而是直接管道式的流入到下一个操作进行处理。

4.RDD五大特性

  1. RDD由很多partition构成,在spark中,计算时,有多少partition 就对应有多少个task来执行
  2. 算子作用在partition上
  3. RDD之间存在一系列依赖关系
  4. 分区器决定数据(key-value)分配至哪个分区,如果RDD里面存的数据是key-value形式,则可以传递一个自定义的Partitioner进行重新分区,比如可以按key的hash值分区
  5. partiton提供最佳的计算位置,优先位置列表,将计算任务分派到其所在处理数据块的存储位置(移动数据不如移动计算)

5.RDD之间的依赖关系(宽依赖与窄依赖)

RDD 中的不同的操作会使得不同 RDD 中的分区会产生不同的依赖关系,主要分为窄依赖(Narrow Dependency)与宽依赖(Wide Dependency)

其中,窄依赖表示的是父 RDD 和子 RDD 之间的一对一关系或者多对一关系,主要包括的操作有 map、filter、union 等;

宽依赖则表示父 RDD 与子 RDD 之间的一对多关系,即一个父 RDD 转换成多个子 RDD,主要包括的操作有 groupByKey、sortByKey 等。


对于窄依赖的 RDD,可以以流水线的方式计算所有父分区,不会造成网络之间的数据混合。
对于宽依赖的 RDD,则通常伴随着 Shuffle 操作,即首先需要计算好所有父分区数据,然后在节点之间进行 Shuffle。
因此,在进行数据恢复时,窄依赖只需要根据父 RDD 分区重新计算丢失的分区即可,而且可以并行地在不同节点进行重新计算。
而对于宽依赖而言,单个节点失效通常意味着重新计算过程会涉及多个父 RDD 分区,开销较大。
此外,Spark 还提供了数据检查点和记录日志,用于持久化中间 RDD,从而使得在进行失败恢复时不需要追溯到最开始的阶段。在进行故障恢复时,Spark 会对数据检查点开销和重新计算 RDD 分区的开销进行比较,从而自动选择最优的恢复策略。

6.划分阶段

Spark 通过分析各个 RDD 的依赖关系生成了 DAG ,再通过分析各个 RDD 中的分区之间的依赖关系来决定如何划分阶段;
具体划分方法是:在 DAG 中进行反向解析,遇到宽依赖就断开,遇到窄依赖就把当前的 RDD 加入到当前的阶段中;将窄依赖尽量划分在同一个阶段中,可以实现流水线计算。

例如在下图中,首先根据数据的读取、转化和行为等操作生成 DAG。然后在执行行为操作时,反向解析 DAG,由于从 A 到 B 的转换和从 B、F 到 G 的转换都属于宽依赖,则需要从在宽依赖处进行断开,从而划分为三个阶段。

把一个 DAG 图划分成多个 “阶段” 以后,每个阶段都代表了一组关联的、相互之间没有 Shuffle 依赖关系的任务组成的任务集合。每个任务集合会被提交给任务调度器(TaskScheduler)进行处理,由任务调度器将任务分发给 Executor 运行。

7.RDD运行过程

RDD 在 Spark 架构中的运行过程(如下图所示):

  1. 创建 RDD 对象;
  2. SparkContext 负责计算 RDD 之间的依赖关系,构建 DAG;
  3. DAGScheduler 负责把 DAG 图反向解析成多个阶段,每个阶段中包含多个任务,每个任务会被任务调度器分发给工作节点上的 Executor 上执行。

8.RDD的创建

1.使用集合创建RDD

sc.parallelize(seq,n):把seq这个集合并行化分片到节点,n可以指定几个分区
sc.makeRDD(seq):把seq这个集合并行化分片到节点,它的实现就是parallelize
sc.makeRDD(seq[(T,seq)]):这种方式可以指定RDD的存放位置

注意:

  • Spark默认会根据集群的情况来设置分区的数量,也可以通过parallelize()第二参数来指定

  • Spark会为每一个分区运行一个任务进行处理

2. 通过加载文件产生RDD

文件中的一行文本作为RDD的一个元素
val distFile=sc.textFile(“file:///home/hadoop/data/a.txt”)

注意:

  • 加载“file://……”文件时,以local运行仅需一份本地文件,以Spark集群方式运行,应保证每个节点均有该文件的本地副本

  • 读取的文件支持目录、压缩文件以及通配符
    比如:

sc.textFile("/my/file")
sc.textFile("/my/file/*.txt")
sc.textFile("/my/file/*.gz")
  • Spark默认访问HDFS
  • Spark默认为HDFS文件的每一个数据块创建一个分区,也可以通过textFile()第二个参数指定,但只能比数据块数量多

3. 创建PairRDD的方法

SparkContext.wholeTextFiles():
可以针对一个目录中的大量小文件返回<filename,fileContent>作为PairRDD
普通RDD:org.apache.spark.rdd.RDD[data_type]
PairRDD:org.apache.spark.rdd.RDD[(key_type,value_type)]

Spark 为包含键值对类型的 RDD 提供了一些专有的操作,比如:reduceByKey()、groupByKey()……
也可以通过键值对集合创建PairRDD:sc.parallelize(List((1,2),(1,3)))

4,其他创建RDD的方法

SparkContext.sequenceFile[K,V]()

Hadoop SequenceFile的读写支持

SparkContext.hadoopRDD()、newAPIHadoopRDD()

从Hadoop接口API创建

SparkContext.objectFile()

RDD.saveAsObjectFile()的逆操作

9.RDD的操作

RDD中操作分为两大类型:转换(transformation)和行动(action)
转换:
通过操作将RDD转换成另外一个RDD;
对于转换操作,RDD的所有转换都不会直接计算结果,仅记录作用于RDD上的操作,当遇到动作算子(Action)时才会进行真正计算

行动:
将一个RDD进行求值或者输出;
本质上动作算子通过SparkContext执行提交作业操作,触发RDD DAG(有向无环图)的执行,所有的动作算子都是急迫型(non-lazy),RDD遇到Action就会立即计算

总结:
所有这些操作主要针对两种类型的RDD:数值RDD和键值对RDD
RDD的所有转换操作都是懒执行的,只有当行动操作出现的时候spark才会真的去运行

RDD的基本讲解就到这里,后面小编会给大家着重说下转换算子和行动算子,记得关注小编!!

猜你喜欢

转载自blog.csdn.net/zp17834994071/article/details/107794941