RDD 弹性分布式数据集是对分布式数据集的一种内存抽象。RDD 在抽象上来说是一种元素集合,包含了数据。RDD 中的元素可以按key 来划分分区,这些分区分布在集群中的不同节点上,从而让RDD 中的数据以不可变的形式被并行操作。RDD的数据默认情况下存放在内存中的,但是在内存资源不足时,Spark 会自动将RDD 数据写入磁盘。
RDD 是一种只读的不可变的数据块,不能直接修改,只能从外部数据转换而来,或者通过在其他RDD 上执行转换操作而创建新的RDD。RDD 里面的数据并不是真实的数据,而是一些元数据信息(这些数据是可序列化的Java 对象),记录了该RDD是通过哪些transformation(转换)得到的,在计算机中使用Lineage 来表示这种血缘结构,Lineage 形成一个有向无环图DAG。整个计算过程中不需要保存中间结果,某个节点上的RDD 分区因为节点故障导致数据丢,这个RDD 会自动通过Lineage 关系重新计算该分区。
RDD 提供了丰富的操作以及支持常见的数据运算,分为转换(transformation)和动作(action)。无论执行了多少次transformation 操作,RDD 都不会真正执行运算,只有当action 操作被执行时,运算才会触发。转换操作,如map,filter,groupByKey,join 都会生成新的RDD,这时新的RDD 便依赖于原有的RDD,这种RDD 之间的依赖关系最终形成了DAG。动作操作,如count,collect 用于执行计算并按指定的形式输出。
RDD 的转换操作确定了RDD 之间的依赖关系,依赖关系分为宽依赖和窄依赖两种。窄依赖是指父RDD 的每个分区只被子RDD 的一个分区所使用,子RDD 分区通常对应一个父RDD 分区。宽依赖是指父RDD 的每个分区都可能被多个子RDD 分区所使用,子RDD 分区通常对应所有的父RDD 分区。宽依赖和窄依赖如图8-3 所示:
图8-3 RDD 宽依赖和窄依赖(www.daowen.com)
宽依赖往往对应着shuffle 操作,需要在运行过程中将同一个父RDD 的分区传入到不同的子RDD 分区中,中间可能涉及多个节点之间的数据传输;而窄依赖的每个父RDD 的分区只会传入到一个子RDD 分区中,通常可以在一个节点内完成转换。
当RDD 分区丢失时:对于窄依赖,由于父RDD 的一个分区只对应一个子RDD分区,这样只需要重算和子RDD 分区对应的父RDD 分区即可,所以这个重算对数据的利用率是100%;对于宽依赖,重算的父RDD 分区对应多个子RDD 分区,这样实际上父RDD 中只有一部分的数据是被用于恢复这个丢失的子RDD 分区的,另一部分对应子RDD 的其它未丢失分区,这就造成了多余的计算;更一般的,宽依赖中子RDD 分区通常来自多个父RDD 分区,极端情况下,所有的父RDD 分区都要进行重新计算。
在Spark 应用中,整个执行流程在逻辑运算之间会形成有向无环图。Action 操作触发之后会将所有累积的操作形成一个有向无环图,然后由调度器调度该图上的任务进行运算。Spark 根据RDD 之间不同的依赖关系切分形成不同的阶段(Stage),一个阶段包含一系列函数进行流水线执行。如图8-4 中的A、B、C、D、E、F,分别代表不同的RDD,RDD 内的一个方框代表一个数据块。数据从HDFS 输入Spark,形成RDD A 和RDD C,RDD A 执行map 操作转换成RDD B,RDD C 上执行map 操作转换成RDD D,RDD D 执行union 操作转换成RDD E,RDD B 和RDD E 进行groupByKey操作转换成RDD F,而在B,E 到F 的过程中又会进行shuffle,最后RDD F 通过函数saveAsSequenceFile 输出保存到HDFS 中。RDD A 和RDD B 属于一个Stage,RDD C,RDD D 和RDD E 属于一个Stage,RDDB,RDD E 和RDD F 属于一个Stage。
图8-4 Spark 运行流程
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。