RDD是什么
RDD是弹性的分布式数据集,一个RDD就是一个分布式对象集合,本质上是一个只读的分区记录集合,每个RDD可以分成多个分区,每个分区就是一个数据集片段,并且一个RDD的不同分区可以被保存到集群中不同的节点上,从而可以在集群中的不同节点上进行并行计算。
上面说得可能有点晦涩,一句话, RDD就是一个数据集合。
但是这个数据集合有一些特点:
- 分区的(partition):由于数据量太大,这个数据集不是存储在一个电脑里面,而是存储在集群中;
- 弹性(resilient): Spark把多个RDD的依赖关系组合成一张DAG图,当某一个RDD数据出错时,能够非常方便地从DAG图推断回来还原数据,RDD有非常强大的容错机制,这就是所谓的弹性;
- 不可变(Immutable): 也就是这个数据集合不能改变,对RDD的每一步转换操作,都生成一个新的RDD;
RDD提供了一组丰富的操作以支持常见的数据运算,分为“转换”和“行动”,转换操作接受RDD并返回RDD,行动操作接受RDD但返回非RDD(即输出一个值或结果)。
RDD典型的执行过程如下:
- RDD读入外部数据源(或者内存中的集合)进行创建;
- RDD经过一系列的“转换”操作,每一次都会产生不同的RDD,供给下一个“转换”使用;
- 最后一个RDD经“行动”操作进行处理,并输出到外部数据源(或者变成Scala集合或标量);
要注意的是:RDD采用了惰性调用,即在RDD的执行过程中,真正的计算发生在RDD的“行动”操作,对于“行动”之前的所有“转换”操作,Spark只是记录下“转换”操作应用的一些基础数据集以及RDD生成的轨迹,而不会触发真正的计算。
关于RDD的各种“转换”和“行动”操作的具体API,可以见Spark常用算子详解,总结得非常详细。
RDD间的依赖关系
RDD中不同的操作会使得不同RDD中的分区会产生不同的依赖。RDD中的依赖关系分为窄依赖与宽依赖。
总体而言,如果父RDD的一个分区只被一个子RDD的一个分区所使用就是窄依赖,否则就是宽依赖。窄依赖典型的操作包括map、filter、union等,宽依赖典型的操作包括groupByKey、sortByKey等。
相对而言,在两种依赖关系中,窄依赖的失败恢复更为高效,它只需要根据父RDD分区重新计算丢失的分区即可(不需要重新计算所有分区),而对于宽依赖而言,单个节点失效通常意味着重新计算过程会涉及多个父RDD分区,开销较大。
RDD运行过程
这里总结一下RDD在Spark架构中的运行过程
- 创建RDD对象;
- SparkContext负责计算RDD之间的依赖关系,构建DAG;
- DAGScheduler负责把DAG图分解成多个阶段,每个阶段中包含了多个任务,每个任务会被任务调度器分发给各个工作节点(Worker Node)上的Executor去执行;