文章目录
- RDD概述
- 1. [RDD是什么](https:///docs/latest/rdd-programming-guide.html)
- 2. 为什么会产生RDD
- RDD的细节
- 1. RDD的属性
- 2. RDD的弹性
- RDD的特点
- 1. RDD可在逻辑上进行分区
- 2. RDD中的数据是只读的
- 3. RDD之间具备依赖性
- 4. 多次使用的RDD可以被缓存
- 5. RDD支持Checkpoint
RDD概述
1. RDD是什么
RDD(Resilient Distributed Dataset)被称为弹性分布式数据集,是 Spark 的基石,是实现 Spark 数据处理的核心抽象。RDD 代表了一个不可变、可分区、里面的元素可并行计算的集合,在Spark中,一切的运算都会抽象为对RDD的操作:即创建RDD、转化已有的RDD以及调用RDD操作进行求值。每个 RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。RDD 可以包含Python、Java、Scala 中任意类型的对象,甚至可以包含用户自定义的对象。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。
RDD支持两种操作:转化操作和行动操作。RDD 的转化操作是返回一个新的 RDD的操作,比如 map()和filter(),而行动操作则是向驱动器程序返回结果或把结果写入外部系统的操作。比如 count() 和 first()。
Spark采用惰性计算模式,RDD只有第一次在一个行动操作(action)中用到时,才会真正计算。Spark可以优化整个计算过程。默认情况下,Spark 的 RDD 会在你每次对它们进行行动操作时重新计算。如果想在多个行动操作中重用同一个 RDD,可以使用 RDD.persist() 让 Spark 把这个 RDD 缓存下来。
2. 为什么会产生RDD
Hadoop的MapReduce是一种基于数据集的工作模式,面向数据,这种工作模式一般是从存储上加载数据集,然后操作数据集,最后写入物理存储设备。数据更多面临的是一次性处理。
MapReduce 的这种方式对数据领域两种常见的操作不是很高效:第一种是迭代式的算法,比如机器学习中ALS、凸优化梯度下降等,这些都需要基于数据集或者数据集的衍生数据反复查询反复操作。MapReduce 这种模式不太合适,即使多 MapReduce 串行处理,性能和时间也是一个问题。数据的共享依赖于磁盘。另外一种是交互式数据挖掘,MapReduce 显然不擅长。
下图是 MapReduce 的迭代过程:
下图是 Spark 的迭代过程:
基于 MapReduce 的数据集的缺陷:网络及磁盘I/O开销太大。所以需要一中新的容错方式来减少网络及磁盘间的开销,这就是产生了 RDD 这种新的数据结构,其能更加高效的支持迭代式计算和有效的数据共享。
明确: RDD 是一种数据结构,名为弹性分布式数据集,是一组数据的集合。与 MapReduce 的工作模式类似,MR 基于数据集工作;Spark 基于 RDD 工作。
RDD的细节
1. RDD的属性
每个RDD是一种独立的数据结构,主要包含以下5个属性
下面是源码中对RDD的解释以及对5个属性的说明:
/**
* A Resilient Distributed Dataset (RDD), the basic abstraction in Spark. Represents an immutable,
* partitioned collection of elements that can be operated on in parallel. This class contains the
* basic operations available on all RDDs, such as `map`, `filter`, and `persist`. In addition,
* [[org.apache.spark.rdd.PairRDDFunctions]] contains operations available only on RDDs of key-value
* pairs, such as `groupByKey` and `join`;
* [[org.apache.spark.rdd.DoubleRDDFunctions]] contains operations available only on RDDs of
* Doubles; and
* [[org.apache.spark.rdd.SequenceFileRDDFunctions]] contains operations available on RDDs that
* can be saved as SequenceFiles.
* All operations are automatically available on any RDD of the right type (e.g. RDD[(Int, Int)])
* through implicit.
*
* Internally, each RDD is characterized by five main properties:
*
* - A list of partitions
* - A function for computing each split
* - A list of dependencies on other RDDs
* - Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
* - Optionally, a list of preferred locations to compute each split on (e.g. block locations for
* an HDFS file)
*/
- 一个分区集合(partitions):数据集的基本组成单位。对于RDD来说,每个分区都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建RDD时指定RDD的分区个数,如果没有指定,那么就会采用默认值。默认值就是程序所分配到的CPU Core的数目。
- 一个计算每个分区的函数:Spark中RDD的计算是以分片为单位的,每个RDD都会实现compute函数以达到这个目的。compute函数会对迭代器进行复合,不需要保存每次计算的结果。
- 一个保存RDD之间依赖关系的集合:RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算。
- 一个Partitioner:RDD的分区函数。Spark中实现了两种类型的分区函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有对于于key-value的RDD,才会有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函数不但决定了RDD本身的分区数量,也决定了parent RDD Shuffle输出时的分区数量。
- 一个用于保存 Partitioner 函数优先位置的列表:存储存取每个Partition的优先位置(preferred location)。对于一个HDFS文件来说,这个列表保存的就是每个Partition所在的块的位置。按照“移动数据不如移动计算”的理念,Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。
有了上述特性,能够非常好地通过RDD来表达分布式数据集,并作为构建DAG图的基础:首先抽象一次分布式计算任务的逻辑表示,最终将任务在实际的物理计算环境中进行处理执行。
RDD是一种逻辑上的概念,一个RDD有多个分区,下图描述了RDD的工作区间:
2. RDD的弹性
前面已经说过RDD(Resilient Distributed Dataset)的全称为弹性分布式数据集,下面将说说RDD的弹性。
- 自动进行内存和磁盘数据存储的切换:Spark优先把数据放到内存中,如果内存放不下,就会放到磁盘里面,程序进行自动的存储切换。
- 基于血统的高效容错机制:在RDD进行转换和动作的时候,会形成RDD的 Lineage 依赖链,当某一个RDD失效的时候,可以通过重新计算上游的RDD来重新生成丢失的RDD数据。
- Task如果失败会自动进行特定次数的重试:RDD的计算任务如果运行失败,会自动进行任务的重新计算,默认次数是4次。
- Stage如果失败会自动进行特定次数的重试:如果Job的某个Stage阶段计算失败,框架也会自动进行任务的重新计算,默认次数也是4次。
- Checkpoint和Persist可主动或被动触发:RDD可以通过Persist持久化将RDD缓存到内存或者磁盘,当再次用到该RDD时直接读取就行。也可以将RDD进行Checkpoint操作,检查点会将数据存储在HDFS中,该RDD的所有父RDD依赖都会被移除。
- 数据调度弹性:Spark把这个JOB的执行模型抽象为通用的有向无环图DAG,可以将多Stage的任务串联或并行执行,调度引擎自动处理Stage的失败以及Task的失败。
- 数据分区的高度弹性:可以根据业务的特征,动态调整数据分区的个数,提升整体的应用执行效率。
总结如下:
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关系。
RDD的特点
RDD表示只读的分区的数据集,对RDD进行改动,只能通过RDD的转换操作,由一个RDD得到一个新的RDD,新的RDD包含了从其他RDD衍生所必需的信息。RDDs之间存在依赖,RDD的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化RDD来切断血缘关系。
1. RDD可在逻辑上进行分区
RDD逻辑上是分区的,每个分区的数据是抽象存在的,计算的时候会通过一个compute函数得到每个分区的数据。如果RDD是通过已有的文件系统构建,则compute函数是读取指定文件系统中的数据,如果RDD是通过其他RDD转换而来,则compute函数是执行转换逻辑将其他RDD的数据进行转换。
2. RDD中的数据是只读的
RDD是只读的,要想改变RDD中的数据,只能在现有的RDD基础上创建新的RDD。如下图所示:
由一个RDD装换到另一个RDD,可以通过丰富的操作算子实现,不再像 MapReduce 那样只能写 Map 和 Reduce,如下图:
简单说明: RDD的操作算子包括两类:一类叫做transformations,它是用来将RDD进行转化,构建RDD的血缘关系;另一类叫做actions,它是用来触发RDD的计算,得到RDD的相关计算结果或者将RDD保存的文件系统中
3. RDD之间具备依赖性
RDDs通过操作算子进行转换,转换得到的新RDD包含了从其他RDDs衍生所必需的信息,RDDs之间维护着这种血缘关系,也称之为依赖。如下图所示,依赖包括两种:一种是窄依赖,RDDs之间分区是一一对应的;另一种是宽依赖,下游RDD的每个分区与上游RDD(也称之为父RDD)的每个分区都有关,是多对多的关系。
通过RDDs之间的这种依赖关系,一个任务流可以描述为DAG(有向无环图),如下图所示,在实际执行过程中宽依赖对应于Shuffle(图中的reduceByKey和join);窄依赖中的所有转换操作可以通过类似于管道的方式一气呵成执行(图中map和union可以一起执行)。
简单说明
- 窄依赖指的是每一个父RDD的 Partition 最多被子RDD的一个 Partition 使用
- 宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition,会引起shuffle
4. 多次使用的RDD可以被缓存
如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用。如下图所示,RDD-1经过一系列的转换后得到RDD-n并保存到hdfs,RDD-1在这一过程中会有个中间结果,如果将其缓存到内存,那么在随后的RDD-1转换到RDD-m这一过程中,就不会计算其之前的RDD-0了。
5. RDD支持Checkpoint
虽然RDD的血缘关系天然地可以实现容错,当RDD的某个分区数据失败或丢失,可以通过血缘关系重建。但是对于长时间迭代型应用来说,随着迭代的进行,RDDs之间的血缘关系会越来越长,一旦在后续迭代过程中出错,则需要通过非常长的血缘关系去重建,势必影响性能。为此,RDD支持checkpoint将数据保存到持久化的存储中,这样就可以切断之前的血缘关系,因为checkpoint后的RDD不需要知道它的父RDDs了,它可以从checkpoint处拿到数据。
给定一个RDD我们至少可以知道如下几点信息:
- 分区数以及分区方式
- 由父RDDs衍生而来的相关依赖信息
- 计算每个分区的数据,计算步骤为
- 如果被缓存,则从缓存中取的分区的数据
- 如果RDD的数据被持久化到checkpoint中,则从checkpoint处恢复数据
- 根据血缘关系计算分区的数据。