RDD 运行原理(二)

来源:互联网 发布:淘宝运营员是做什么的 编辑:程序博客网 时间:2024/06/05 03:04

在学习Spark 之前笔者是先大致了解Spark的一些基本概念,以及在各种网站进行浏览一般,在大致对Spark整体知识有了一定的了解后再进行比较系统的学习与了解。在了解的过程中发现学习Spark前对RDD原理的理解是比较重要的,因而笔者是先了解RDD 相关原理然后再进行相应的学习。

一.设计背景

许多迭代式算法(比如机器学习、图算法等)和交互式数据挖掘工具,共同之处是,不同计算阶段之间会重用中间结果。目前的MapReduce框架都是把中间结果写入到HDFS中,带来了大量的数据复制、磁盘IO和序列化开销。 RDD就是为了满足这种需求而出现的,它提供了一个抽象的数据架构,我们不必担心底层数据的分布式特性, 只需将具体的应用逻辑表达为一系列转换处理,不同RDD之间的转换操作形成依赖关系,可以实现管道化,避免中间数据存储。

二.RDD概念

RDD是一种只读的、分区的记录集合。具体来说,RDD具有以下一些特点: 创建:只能通过转换(transformation,如map/filter/groupBy/join等, 区别于动作action)从两种数据源中创建RDD:1)稳定存储中的数据;2)其他RDD。只读:状态不可变,不能修改分区:支持使RDD中的元素根据那个key来分区(partitioning),保存到多个结点上。还原时只会重新计算丢失分区的数据,而不会影响整个系统。 路径:在RDD中叫世族或血统(lineage),即RDD有充足的信息关于它是如何从其他RDD产生而来的。 持久化:支持将会·被重用的RDD缓存(如in-memory或溢出到磁盘) 延迟计算:像DryadLINQ一样,Spark也会延迟计算RDD,使其能够将转换管道化(pipeline transformation) 操作:丰富的动作(action),count/reduce/collect/save等。 关于转换(transformation)与动作(action)的区别,前者会生成新的RDD,而后者只是将RDD上某项操作的结果返回给程序,而不会生成新的RDD。RDD典型的执行过程如下:RDD读入外部数据源进行创建 RDD经过一系列的转换(Transformation)操作,每一次都会产生不同的RDD,供给下一个转换操作使用,  最后一个RDD经过“动作”操作进行转换,并输出到外部数据源 这一系列处理称为一个Lineage(血缘关系),即DAG拓扑排序的结果优点:惰性调用、管道化、避免同步等待、不需要保存中间结果、每次操作变得简单

这里写图片描述

三.RDD特性

Spark采用RDD以后能够实现高效计算的原因主要在于:(1)高效的容错性    现有容错机制:数据复制或者记录日志     RDD:血缘关系、重新计算丢失分区、无需回滚系统、重算过程在不同节点之间并行、只记录粗粒度的操作。     RDD提供基于粗粒度转换(coarse-grained transformation)的接口,例如map、filter、join,能够将同一操作施加到许多数据项上。     于是通过记录这些构建数据集(lineage世族)的粗粒度转换的日志,而非实际数据,就能够实现高效的容错。     当某个RDD丢失时,RDD有充足的关于丢失的那个RDD是如何从其他RDD产生的信息,从而通过重新计算来还原丢失的数据,避免了数据复制的高开销。(2)中间结果持久化到内存,数据在内存中的多个RDD操作之间进行传递,避免了不必要的读写磁盘开销。(3)存放的数据可以是Java对象,避免了不必要的对象序列化和反序列化。

四.RDD之间的依赖关系

RDD的依赖关系分为窄依赖与宽依赖。 窄依赖表现为一个父RDD的分区对应于一个子RDD的分区或多个父RDD的分区对应于一个子RDD的分区。 宽依赖则表现为存在一个父RDD的一个分区对应一个子RDD的多个分区。 

这里写图片描述

五.Stage的划分

Spark通过分析各个RDD的依赖关系生成了DAG,再通过分析各个RDD中的分区之间的依赖关系来决定如何划分Stage,具体划分方法是: 在DAG中进行反向解析,遇到宽依赖就断开。 遇到窄依赖就把当前的RDD加入到Stage中。 将窄依赖尽量划分在同一个Stage中,可以实现流水线计算。

具体例子如下图所示:

这里写图片描述

如上图,假设从 HDFS 中读入数据生成 3 个不同的 RDD(即 A、C 和 E),通过一系列转换操作后再将计算结果保存回 HDFS。对 DAG 进行解析时,在依赖图中从右往左进行反向解析,可以得到三个 Stage,可以看出,在 Stage2 中,从 map 到 union 都是窄依赖, 这两步操作可以形成一个流水线操作,比如,分区 7 通过 map 操作生成的分区 9,可以不用 等待分区 8 到分区 9 这个转换操作的计算结束,而是继续进行 union 操作,转换得到分区 13,这样流水线执行大大提高了计算的效率。从上述划分可以看出,Stage 的类型包括两种:ShuffleMapStage 和 ResultStage,具体如下: (1)ShuffleMapStage:不是最终的 Stage,在它之后还有其他 Stage,所以,它的输出一定需要经过 Shuffle 过程,并作为后续 Stage 的输入;这种 Stage 是以 Shuffle 为输出边界,其输入边界可以是从外部获取数据,也可以是另一个 ShuffleMapStage 的输出,其输出可以是另一个 Stage 的开始;在一个 Job 里可能有该类型的 Stage,也可能没有该类型 Stage; (2)ResultStage:最终的 Stage,没有输出,而是直接产生结果或存储。这种 Stage 是直接输出结果,其输入边界可以是从外部获取数据,也可以是另一个 ShuffleMapStage 的输出。在一个 Job 里必定有该类型 Stage。 因此,一个 Job 含有一个或多个 Stage,其中至少含有一个 ResultStage。

六.RDD运行过程

通过上述对RDD概念、依赖关系和Stage划分的介绍,结合之前介绍的Spark运行基本流程,再总结一下RDD在Spark架构中的运行过程: (1)创建RDD对象; (2)SparkContext负责计算RDD之间的依赖关系,构建DAG; (3)DAGScheduler负责把DAG图分解成多个Stage,每个Stage中包含了多个Task,每个Task会被TaskScheduler分发给各个WorkerNode上的Executor去执行。

这里写图片描述

七、Spark ADD的基本操作

1、将README.md上传到该目录上
/home/centosm/test/README.md

2、在Spark程序中必须创建一个SparkContext对象,该对象是Spark程序的入口,负责创建RDD、启动任务等。在启动Spark Shell后,该对象会自动创建,可以通过变量sc进行访问。
作为示例,我们选择以Spark安装目录中的“README.md”文件作为数据源新建一个RDD,代码如下:

scala> var textFile = sc.textFile("file:///home/centosm/test/README.md") textFile: org.apache.spark.rdd.RDD[String] = file:///home/centosm/test/README.md MapPartitionsRDD[13] at textFile at <console>:24

使用action API - count()可以统计该文本文件的行数,命令如下:

scala> textFile.count()res10: Long = 69

Spark属于MapReduce计算模型,因此也可以实现MapReduce的计算流程,如实现单词统计,可以使用如下的命令实现:

scala> val wordCounts = textFile.flatMap(line => line.split(" ")).map(word => (word, 1)).reduceByKey((a, b) => a + b)wordCounts: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[16] at reduceByKey at <console>:26scala> wordCounts.count()res12: Long = 277                                                               scala> wordCounts.collect()  res13: Array[(String, Int)] = Array((package,1), ([params].,1), (this,1), (Because,1), (Python,2), (cluster.,1), (its,1), (guide,1), ("Useful,1), ("Building,1), (general,3), (have,1), (pre-built,1), (YARN,,1), (locally,2), (changed,1), (Contribution,1), (sc.parallelize(1,1), (only,1), (locally.,1), (several,1), (This,2), (basic,1), (Configuration,2), (learning,,1), (documentation,3), (first,1), (graph,1), (Hive,2), (info,1), (Version",1), ("yarn",1), (prefer,1), (SparkPi,2), (engine,1), (version,1), (file,1), (documentation,,1), (http://spark.apache.org/,1), (MASTER,1), (example,3), (are,1), (systems.,1), (params,1), (scala>,1), (DataFrames,,1), (provides,1), (refer,2), (configure,1), (Interactive,2), (R,,1), (can,7), (build,4), (when,1), (easiest,1), (Apache,2), (thread,1), (how,3), (p...scala> 

首先使用flatMap()将每一行的文本内容通过空格进行划分为单词;
再使用map()将单词映射为(K,V)的键值对,其中K为单词,V为1;
最后使用reduceByKey()将相同单词的计数进行相加,最终得到该单词总的出现的次数。
collect() 以数组的形式返回数据集中的所有元素

原创粉丝点击