学习spark最基本的概念就时RDD(Resilient Distributed Datasets弹性分布式数据集)


RDD五大特性


spark rdd的特性 spark rdd partition_spark

我画了一个丑丑的图,这里我们将RDD图形化一下,更容易理解

在RDD源码里面,它规定了五大特性:

  • A list of partitions
  • 向图中一样由一系列分区组成,分割分区在不同节点之上
  • A function for computing each split
  • 每个分片都有函数可以作用在上面
  • A list of dependencies on other RDDs
  • RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算。
  • Optionally, a Partitioner for key-value RDDs
  • 一个partitioner,即RDD的分片函数。当前Spark中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个基于范围的RangePartitioner。只有对于key-value的RDD,才会有Partitioner,非key-value的RDD的Partitioner的值是None。Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量。
  • Optionally, a list of preferred locations to compute each split on
  • 一个列表,存储存取每个Partition的优先位置(preferred location)。对于一个HDFS文件来说,这个列表保存的就是每个Partition所在的块的位置。按照“移动数据不如移动计算”的理念,Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。

这五大特性的前三点已经很好的解释了什么是一个RDD

实际上我把理解成RDD不存储数据,partition存储的是一种计算逻辑,先介绍算子在告诉大家为啥可以这么理解

 


算子


先来看看什么是算子

 

spark rdd的特性 spark rdd partition_spark_02

这个时官方论文里面给的算子,算子主要分为三种:

  • TRansformations:
  • 由图中可以看出,Transformations算子都由一个RDD转换成了另一个RDD,在Spark里面时懒加载的,就是由这些RDD算子构成的有向无环图DAG不会马上执行,一直要遇到action算子才触发执行
  • action:
  • 触发算子,遇到这样的算子,会立即将前面的有向无环图的transfromation算子执行了。
  • 持久化算子:
  • 和触发算子差不多,能够让数据落地,但不会触发执行,等到action算子有了的时候,就会将持久化算子作用的RDD持久化到存储空间,为了容错而避免重复计算。

 

 

 


下面就看看什么时宽依赖和窄依赖

spark rdd的特性 spark rdd partition_大数据_03

宽依赖:想象成一种非独生关系,如右边的groupBykey算子,导致一个父分区可以有多个子分区,这就是宽依赖

窄依赖:想象成一种独生关系,左边的map,filter,union算子,一个父分区只有一个唯一的子分区

记住这个就相当于记住了精髓,不管别人怎么问,怎么变,只要看是否独生关系就可以知道了。

那么这个什么时候构成了宽依赖,什么时候构成了窄依赖呢。

一般来说,就是有shuffle的算子就是宽依赖了,比如groupBykey,reduceBykey等等,以后会有专门的博客介绍各种算子的。

 

 

 


其他的重要的术语

spark rdd的特性 spark rdd partition_大数据_04

我们来看这张图

job:job就是当action算子触发时所形成的一些列任务。比如上面这张图,如果在G后面用一个collect的算子来触发,那么前面的(准确来说是上一个action算子开始)所有的算子任务构成了一个job

stage:stage是根据宽依赖之间来划分的,比如上边有宽依赖groupBy和join将一个job划分成stageA     stage(BG)   stage(CDFE),三个stage,stage有个并行度的概念,而且stage的并行度是由final partition的流水线决定的,比如stage2的并行度是4,虽然说RDDC和RDDD之间只有2.。

task:task就是某一个stage中的并行度中的一条流水线,比如stage2中有4个task,也就是4条流水线,这几个task是要分发给不同的worker(Executor)去执行的。

另外我们要搞清楚的是,spark的执行过程是基于pipeline管道模式的,比如上面的图RDD C从某个地方读取一条数据,C的任何一个分区就直接读取一条条数据去执行,图中是执行map操作,做完直接交给D中的一个分区,再交给F执行,也就是说,数据是一条条再task这条流水线里面跑动执行的,到了F之后执行的join算子,就让F的结果暂时停下来存入到内存,也就说基本再C,D,F之间基本不占用什么内存。

而且,就是因为pipeline这种管道模式的执行流水线,我们可以知道RDD不是等上一个RDD执行完才执行下一个RDD的,而是流水性的不断往前,所以与其说RDD存储数据,不如说它的分区存储的是一种处理数据的逻辑。

 

 


弹性


  1. 弹性之一:自动的进行内存和磁盘数据存储的切换; RDD有不同的存储级别的,比如内存,或者内存和磁盘,或者磁盘等等
  2. 弹性之二:基于Lineage的高效容错(第n个节点出错,会从第n-1个节点恢复,血统容错),这个容错性非常重要
  3. 弹性之三:Task如果失败会自动进行特定次数的重试(默认4次);
  4. 弹性之四:Stage如果失败会自动进行特定次数的重试(可以只运行计算失败的阶段);只计算失败的数据分片;
  5. 弹性之五:checkpoint和persist ,这个是spark里面的持久化算子,能够将数据手动落地到内存或者磁盘。
  6. 弹性之六:数据调度弹性:DAG TASK 和资源 管理无关 ,即分为了资源调度和任务调度
  7. 弹性之七:数据分片的高度弹性(人工自由设置分片函数),repartition 就增加了并行度。

下面就来看看每个弹性体现在哪里:

1:在spark里面有不同的算子能够操作RDD之间的数据逻辑或者执行结构。在mapreduce里面一般都要在不同节点之间的磁盘之间进行交流,而我们的spark可以自动的进行内存和磁盘数据存储的切换

spark rdd的特性 spark rdd partition_大数据_05

这是RDD算子的不同存储级别。

2:高效的容错:我们以前的数据库,如果出现一个操作错误,比如事务管理,一旦出错,就要让事务进行回滚,一般也需要一些日志文件,让其回滚操作,它的开销是非常的大的。尤其是分布式的,涉及到各种磁盘节点之间的网络传输。而我们spark就不同,它采用的是一种数据更新的方式,一条流水线的工作能够让不同节点之间减少网络传输,而且在内存里面之间计算要快很多,开销也相对简单了。

spark rdd的特性 spark rdd partition_spark rdd的特性_06

我们仔细看看这张图,在stage2中,有4条流水线,加入这几条流水线工作复杂,当join算子启动时,进行了shuffle,那么F这个RDD他就落地了,保存在了内存中,如果哪一天GRDD丢失了,我们只需要从B和F中拿到重新计算,而不需要进行回滚加大开销,也不需要从C到D到F重新计算。

弹性3,弹性4:这个属于源码里面任务调度资源调度的东西,以后分析源码会说到。

弹性5:让数据落地的方式有两种:一种是进行了shuffle的算子,也就是有宽依赖的算子启动了,就会让RDD数据落地。还有一种就是将RDD手动落地的算子(cache,persist,checkpoint)。前面的弹性二重新计算数据时不需要重新计算F就是因为join算子进行了shuffle,F数据落地,而不需要将CDF重新计算出F。再比如一串RDD的执行逻辑为:P->Q->T->K,加入P->Q->T的执行逻辑特别麻烦,那么我们就可以这样使用cache将T算子缓存,以后T再用时就直接将T从缓存里面拿出来,不用进行复杂的开销计算了,persist就是增加了不同的存储级别,而cache就是persist默认的存储级别(仅仅限于Memory),而checkpoint就是将前面的依赖链给毁掉,将checkpoint点的RDD存入高可用的存储位置。checkpoint我还不是很清楚,以后会慢慢搞清楚的。

弹性六:资源调度和任务调度是一个连续的过程,由提交程序的时间点分隔

spark rdd的特性 spark rdd partition_java_07

资源调度:当我们安装好spark集群后(master,worker1,worker2,worker3)-master可以认为是ClusterManager

  • worker会向master汇报资源,master掌握worker资源
  • 当Driver端new一个SparkContext时,会创建DAGScheduler和TaskSchuduler,DAG
  • TaskScheduler负责向master申请资源
  • master找到资源充足的worker,在对应的worker上启动Executor,Executor就反向注册到Driver

任务调度:当Executor反向注册到Driver之后

  • Driver掌握了worker的资源
  • 在一个job(由action算子划分)之间,DAGScheduler将一个job划分成许多stage(由宽依赖分割,即看有没有shuffle)
  • DAGScheduler将stage以TaskSet的方式发给TaskScheduler
  • TaskScheduler遍历TaskSet,拿到一个task,发送到worker的Executor中执行
  • TaskScheduler监控task,回收结果。

弹性7:请注意我们的一个RDD是由多个分区构成的

spark rdd的特性 spark rdd partition_spark rdd的特性_08

就像这个,一个大方框就是一个RDD,由多个分区蓝色块组成,一般来说,我们可以适当增加分区,也就是增加节点,同时处理更多的数据,来加快速度的。这个分区个数是可以人工设置的。