RDD概念/特性
许多迭代式算法(比如机器学习、图算法等)和交互式数据挖掘工具,共同之处是不同计算阶段之间会重用中间结果, MapReduce框架把中间结果写入到稳定存储(如磁盘)中,带来大量的数据复制、磁盘IO和序列化开销。
RDD就是为了满足这种需求而出现的,它提供了一个抽象的数据架构,开发者不必担心底层数据的分布式特性,只需将具体的应用逻辑表达为一系列转换处理,不同RDD之间的转换操作形成依赖关系,可以实现管道化,避免中间数据存储。一个RDD就是一个分布式对象集合,本质上是一个只读的分区记录集合,每个RDD可分成多个分区,每个分区就是一个数据集片段,并且一个RDD的不同分区可以被保存到集群中不同的节点上,从而可以在集群中的不同节点上进行并行计算。
RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,不能直接修改,只能基于稳定的物理存储中的数据集创建RDD,或者通过在其他RDD上执行确定的转换操作(如map、join和group by)而创建得到新的RDD。
RDD提供了丰富的操作以支持常见数据运算,分“转换”(Transformation)和“动作”(Action)两种类型;RDD提供的转换接口都非常简单,都是类似map、filter、groupBy、join等粗粒度的数据转换操作,而不是针对某个数据项的细粒度修改(不适合网页爬虫),表面上RDD的功能很受限、不够强大,实际上RDD已经被实践证明可以高效地表达许多框架的编程模型(比如MapReduce、SQL、Pregel);Spark用Scala语言实现了RDD的API,程序员可以通过调用API实现对RDD的各种操作
RDD典型的执行过程如下,这一系列处理称为一个Lineage(血缘关系),即DAG拓扑排序的结果:
RDD读入外部数据源进行创建;
RDD经过一系列的转换(Transformation)操作,每一次都会产生不同的RDD,供给下一个转换操作使用;
最后一个RDD经过“动作”操作进行转换,并输出到外部数据源优点:惰性调用、管道化、避免同步等待、不需要保存中间结果、操作简单;
Spark采用RDD以后能够实现高效计算的原因主要在于:
(1)高容错性:血缘关系、重新计算丢失分区、无需回滚系统、重算过程在不同节点之间并行、只记录粗粒度的操作;
(2)中间结果持久化到内存:数据在内存中的多个RDD操作之间进行传递,避免了不必要的读写磁盘开销;
(3)存放的数据是Java对象:避免了不必要的对象序列化和反序列化;
RDD依赖关系
Spark通过分析各个RDD的依赖关系生成了DAG,并根据RDD 依赖关系把一个作业分成多个阶段,阶段划分的依据是窄依赖和宽依赖,窄依赖可以实现流水线优化,宽依赖包含Shuffle过程,无法实现流水线方式处理。
窄依赖表现为一个父RDD的分区对应于一个子RDD的分区或多个父RDD的分区对应于一个子RDD的分区;宽依赖则表现为存在一个父RDD的一个分区对应一个子RDD的多个分区。
逻辑上每个RDD 操作都是一个fork/join(一种用于并行执行任务的框架),把计算fork 到每个RDD 分区,完成计算后对各个分区得到的结果进行join 操作,然后fork/join下一个RDD 操作;
RDD Stage划分:Spark通过分析各个RDD的依赖关系生成了DAG,再通过分析各个RDD中的分区之间的依赖关系来决定如何划分Stage,具体方法:
在DAG中进行反向解析,遇到宽依赖就断开;
遇到窄依赖就把当前的RDD加入到Stage中;
将窄依赖尽量划分在同一个Stage中,可以实现流水线计算;