Spark 的核心是创建在统一的抽象 RDD 之上,基于 RDD 的转换和行动操做使得 Spark 的各个组件能够无缝进行集成,从而在同一个应用程序中完成大数据计算任务。算法
在实际应用中,存在许多迭代式算法和交互式数据挖掘工具,这些应用场景的共同之处在于不一样计算阶段之间会重用中间结果,即一个阶段的输出结果会做为下一个阶段的输入。而 Hadoop 中的 MapReduce 框架都是把中间结果写入到 HDFS 中,带来了大量的数据复制、磁盘 IO 和序列化开销,而且一般只支持一些特定的计算模式。而 RDD 提供了一个抽象的数据架构,从而让开发者没必要担忧底层数据的分布式特性,只需将具体的应用逻辑表达为一系列转换处理,不一样 RDD 之间的转换操做造成依赖关系,能够实现管道化,从而避免了中间结果的存储,大大下降了数据复制、磁盘 IO 和序列化开销。网页爬虫
一个 RDD 就是一个分布式对象集合,提供了一种高度受限的共享内存模型,其本质上是一个只读的分区记录集合,不能直接修改。每一个 RDD 能够分红多个分区,每一个分区就是一个数据集片断,而且一个 RDD 的不一样分区能够保存到集群中不一样的节点上,从而能够在集群中的不一样节点上进行并行计算。网络
RDD 提供了一组丰富的操做以支持常见的数据运算,分为“行动”(Action)和“转换”(Transformation)两种类型,前者用于执行计算并指定输出的形式,后者指定 RDD 之间的相互依赖关系。RDD 提供的转换接口都很是简单,都是相似 map
、filter
、groupBy
、join
等粗粒度的数据转换操做,而不是针对某个数据项的细粒度修改。所以,RDD 比较适合对于数据集中元素执行相同操做的批处理式应用,而不适合用于须要异步、细粒度状态的应用,好比 Web 应用系统、增量式的网页爬虫等。架构
RDD 的典型的执行过程以下:框架
RDD 采用了惰性调用,即在 RDD 的执行过程当中,全部的转换操做都不会执行真正的操做,只会记录依赖关系,而只有遇到了行动操做,才会触发真正的计算,并根据以前的依赖关系获得最终的结果。异步
下面以一个实例来描述 RDD 的实际执行过程,以下图所示,开始从输入中建立了两个 RDD,分别是 A 和 C,而后通过一系列的转换操做,最终生成了一个 F,这也是一个 RDD。注意,这些转换操做的执行过程当中并无执行真正的计算,基于建立的过程也没有执行真正的计算,而只是记录的数据流向轨迹。当 F 执行了行为操做并生成输出数据时,Spark 才会根据 RDD 的依赖关系生成有向无环图(DAG),并从起点开始执行真正的计算。正是 RDD 的这种惰性调用机制,使得转换操做获得的中间结果不须要保存,而是直接管道式的流入到下一个操做进行处理。分布式
整体而言,Spark 采用 RDD 之后可以实现高效计算的主要缘由以下:ide
高效的容错性。在 RDD 的设计中,只能经过从父 RDD 转换到子 RDD 的方式来修改数据,这也就是说咱们能够直接利用 RDD 之间的依赖关系来从新计算获得丢失的分区,而不须要经过数据冗余的方式。并且也不须要记录具体的数据和各类细粒度操做的日志,这大大下降了数据密集型应用中的容错开销。工具
中间结果持久化到内存。数据在内存中的多个 RDD 操做之间进行传递,不须要在磁盘上进行存储和读取,避免了没必要要的读写磁盘开销;oop
存放的数据能够是 Java 对象,避免了没必要要的对象序列化和反序列化开销。
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 分区的开销进行比较,从而自动选择最优的恢复策略。
Spark 经过分析各个 RDD 的依赖关系生成了 DAG ,再经过分析各个 RDD 中的分区之间的依赖关系来决定如何划分阶段,具体划分方法是:在 DAG 中进行反向解析,遇到宽依赖就断开,遇到窄依赖就把当前的 RDD 加入到当前的阶段中;将窄依赖尽可能划分在同一个阶段中,能够实现流水线计算。例如在下图中,首先根据数据的读取、转化和行为等操做生成 DAG。而后在执行行为操做时,反向解析 DAG,因为从 A 到 B 的转换和从 B、F 到 G 的转换都属于宽依赖,则须要从在宽依赖处进行断开,从而划分为三个阶段。把一个 DAG 图划分红多个 “阶段” 之后,每一个阶段都表明了一组关联的、相互之间没有 Shuffle 依赖关系的任务组成的任务集合。每一个任务集合会被提交给任务调度器(TaskScheduler)进行处理,由任务调度器将任务分发给 Executor 运行。
经过上述对 RDD 概念、依赖关系和阶段划分的介绍,结合以前介绍的 Spark 运行基本流程,这里再总结一下 RDD 在 Spark 架构中的运行过程(以下图所示):