RDD 运行原理(二)

在学习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 RDD的基本操作

  • 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>:26

scala> 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() 以数组的形式返回数据集中的所有元素
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值