在Spark集群背后,有一个非常重要的分布式数据架构,即弹性分布式数据集(Resilient Distributed DataSet,RDD),它是逻辑集中的实体,在集群中的多台集群上进行数据分区。通过对多台机器上不同RDD分区的控制,能够减少机器之间的数据重排(Data Shuffle)。Spark提供了“partitionBy”运算符,能够通过集群中多台机器之间对原始RDD进行数据再分配来创建一个新的RDD。RDD是Spark的核心数据结构,通过RDD的依赖关系形成Spark的调度顺序。通过RDD的操作形成整个Spark程序。
RDD有四种创建方式,如下:
1、从Hadoop文件系统(或与Hadoop兼容的其他持久化存储系统,如Hive、Cassandra、HBase)输入(如HDFS)创建。
2、从父RDD转换得到新的RDD。
3、调用SparkContext()方法的parallelize,将Driver上的数据集并行化,转化为分布式的RDD。
4、更改RDD的持久性(persistence),例如cache()函数。默认RDD计算后会在内存中清除。通过cache()函数将计算后的RDD缓存在内存中。
RDD的两种操作算子
对于RDD可以有两种计算操作算子:Transformation(变换)与Action(行动)。
1、Transformation(变化)算子
Transformation操作是延迟计算的,也就是说从一个RDD转换生成另一个RDD的转换操作不是马上执行,需要等到有Actions操作时才真正触发运算。
2、Action(行动)算子
Action算子会触发Spark提交作业(Job),并将数据输出到Spark系统。
算子的分类:
1、Value数据类型的Transformation算子,这种变换算子不触发提交作业,针对处理的数据项是Value型的数据。
处理数据类型为Value型的Transformation算子可以根据RDD变换算子的输入分区与输出分区关系分为以下几种类型:
1、输入分区与输出分区一对一型。
2、输入分区与输入分区多对一型。
3、输入分区与输出分区多对多型。
4、输出分区为输入分区子集型。
5、一种特殊的输入与输出分区一对一的算子类型,即Cache型。Cache。
2、Key-Value数据类型的Transformation算子,这种变换算子不触发提交作业,针对处理的数据项是Key-Value型的数据对。
处理数据类型为Key-Value型的算子,大致分为以下几种类型:
1、输入分区与输出分区一对一型。
2、聚集(单个RDD聚集/多个RDD聚集)。
3、连接(对两个需要连接的RDD进行cogroup函数操作的Join/左外连接或右外连接(在Join的基础上进行))。
3、Action算子,这类算子会触发SparkContext提交Job作业。
本质上在Actions算子中通过SparkContext执行提交作业的runJob操作,触发了RDD DAG(有向无环图)的执行。根据Action算子的输出空间将Action算子分为以下几种类型:
1、无输出(对RDD中每个元素都应用f函数操作,不返回RDD和Array,而是返回Unit。即foreach。)。
2、HDFS(调用saveAsTextFile函数将数据输出存储到HDFS中;saveAsObjectFile将分区中的固定长度一组元素组成的一个Array然后将这个Array序列化,映射为Key-Value的元素写入HDFS为SequenceFile格式的文件)。
3、Scala集合和数据类型(collect;collectAsMap;reduceByKeyLocally;lookup;count;top;reduce;folt;aggregate)。
RDD的重要内部属性
1、分区列表。
2、计算每个分片的函数。
3、对父RDD的依赖列表。
4、对Key-Value数据类型RDD的分区器,控制分区策略和分区数,
5、每个数据分区的地址列表(如HDFS上的数据块的地址)。
Spark数据存储的核心是弹性分布式数据集(RDD)。RDD可以被抽象的理解为一个大的数组(Array),但这个数组是分布在集群上的。逻辑上RDD的每个分区叫一个Partition。在Spark的执行过程中,RDD经历一个个的Transformation算子之后,最后通过Action算子进行触发操作。逻辑上每经历一次变换,就会将RDD转换为一个新的RDD,RDD之间通过Lineage产生依赖关系。这个关系在容错中有很重要的作用。变换的输入和输出都是RDD。RDD会被划分成很多的分区分布到集群的多个节点中。分区是个逻辑概念,变换前后的新旧分区在物理上可能是同一块内存存储。这是很重要的优化,以防止函数式数据不变性(Immutable)导致的内存需求无限扩张。有些RDD是计算的中间结果,其分区并不一定有相应的内存或磁盘数据与之对应,如果要迭代使用数据,可以调用cache()函数缓存数据。
在物理上,RDD对象实质上是一个元数据结构,存储着Block、Node等的映射关系和其他元数据信息。一个RDD就是一组分区,在物理数据存储上,RDD的每个分区对应的就是一个Block,Block可以存储在内存,当内存不够的时候可以存储到磁盘上。每个Block中存储着RDD所有数据项的一个子集,暴露给用户的可以是一个Block的迭代器(例如用户可以通过mapPartitions获得分区迭代器进行操作),也可以就是一个数据项(例如通过map函数对每个数据项进行并行计算)。如果是从HDFS等外部存储作为输入数据源,数据按照HDFS中的数据分布策略进行数据分区,HDFS中的一个Block对应SPark的一个分区。同事Spark支持重分区,数据通过Spark默认的或者用户自定义的分区器决定数据块分布在那些节点。例如:支持hash分区(按照数据项Key值取hash值,hash值相同的元素放入同一个分区内)和Range分区(将属于同一数据范围的数据放入同一分区)的策略。
总结:Spark将应用程序整体翻译为一个DAG有向无环图进行调度和执行。相比MapReduce,Spark提供了更加优化和复杂的执行流。