大数据笔记--Spark(第二篇)

目录

一、DAG概念

1、概述

2、详解

二、RDD的依赖关系

1、概括

2、窄依赖

3、宽依赖

4、Shuffle概述

三、DAG的生成与Stage的划分

1、DAG的生成

2、Saprk的Stage(阶段)

3、Saprk的Job和Task

4、可视化理解窄依赖和宽依赖

四、Spark框架核心概念


一、DAG概念

1、概述

Spark会根据用户提交的计算逻辑中的RDD的转换和动作来生成RDD之间的依赖关系,同时这个计算链也就生成了逻辑上的DAG。

2、详解

我们一单词统计案例为例子,描述DAG生成的实现过程

Spark Scala版本的Word Count程序如下:

1: val file=sc.textFile("hdfs://hadoop01:9000/hello1.txt")

2: val counts = file.flatMap(line => line.split(" "))

3:            .map(word => (word, 1))

4:            .reduceByKey(_ + _)

5: counts.saveAsTextFile("hdfs://...")

filecounts都是RDD,其中file是从HDFS上读取文件并创建了RDD,而counts是在file的基础上通过flatMap、map和reduceByKey这三个RDD转换生成的,最后counts调用了动作saveAsTextFile,用户的计算逻辑就从这里开始提交的集群进行计算。那么上面这5行代码的具体实现是什么呢?

①、行1:sc是org.apache.spark.SparkContext的实例,它是用户程序和Spark的交互接口,会负责连接到集群管理者,并根据用户设置或者系统默认设置来申请计算资源,完成RDD的创建等。

sc.textFile("hdfs://...")就完成了一个org.apache.spark.rdd.HadoopRDD的创建,并且完成了一次RDD的转换:通过map转换到一个org.apache.spark.rdd.MapPartitions-RDD。也就是说,file实际上是一个MapPartitionsRDD,它保存了文件的所有行的数据内容。

②、行2:将file中的所有行的内容,以空格分隔为单词的列表,然后将这个按照行构成的单词列表合并为一个列表。最后,以每个单词为元素的列表被保存到MapPartitionsRDD。

③、行3:将第2步生成的MapPartittionsRDD再次经过map将每个单词word转为(word,1)的元组。这些元组最终被放到一个MapPartitionsRDD中。

④、行4:首先会生成一个MapPartitionsRDD,起到map端combiner的作用;然后会生成一个ShuffledRDD,它从上一个RDD的输出读取数据,作为reducer的开始;最后,还会生成一个MapPartitionsRDD,起到reducer端reduce的作用。

⑤、行5:向HDFS输出RDD的数据内容。最后,调用org.apache.spark.SparkContext#runJob向集群提交这个计算任务。

 注意:这个DAG记录了RDD之间的依赖关系,借助RDD之间的依赖关系,可以实现数据容错。

RDD之间的关系可以从两个维度来理解:一个是RDD是从哪些RDD转换而来,也就是RDD的parent RDD(s)是什么;还有就是依赖于parent RDD(s)的哪些Partition(s)。这个关系,就是RDD之间的依赖,org.apache.spark.Dependency。根据依赖于parent RDD(s)的Partitions的不同情况,Spark将这种依赖分为两种,一种是宽依赖,一种是窄依赖

二、RDD的依赖关系

1、概括

RDD和它依赖的parent RDD(s)的关系有两种不同的类型,即窄依赖(narrow dependency)和宽依赖(wide dependency)。

①、窄依赖指的是每一个parent RDD的Partition最多被子RDD的一个Partition使用:

窄依赖特点:父分区只被一个子分区所依赖,可以理解为一对一的关系。

针对窄依赖,可以理解为仅是根据转换规则进行转换,并不涉及其他的操作,比如分组。典型的窄依赖方法:map、flatMap、mapPartitions、filter。

②、宽依赖指的是多个子RDD的Partition会依赖同一个parent RDD的Partition。

宽依赖特点:父分区被多个子分区依赖,可以理解为一对多的关系。

针对宽依赖,会产生分组操作,即底层会有Shuffle过程。产生宽依赖的方法:groupBy,groupByKey,reduceByKey,distinct。

2、窄依赖

对于窄依赖操作,它们只是将Partition的数据根据转换的规则进行转化,并不涉及其他的处理,可以简单地认为只是将数据从一个形式转换到另一个形式。

窄依赖底层的源码:

abstract class NarrowDependency[T](_rdd: RDD[T]) extends Dependency[T] {
    //返回子RDD的partitionId依赖的所有的parent RDD的Partition(s)
    def getParents(partitionId: Int): Seq[Int]
    override def rdd: RDD[T] = _rdd
}
 
class OneToOneDependency[T](rdd: RDD[T]) extends NarrowDependency[T](rdd) {
    override def getParents(partitionId: Int) = List(partitionId)
}

所以对于窄依赖,并不会引入昂贵的Shuffle。所以执行效率非常高。如果整个DAG中存在多个连续的窄依赖,则可以将这些连续的窄依赖整合到一起连续执行,中间不执行shuffle 从而提高效率,这样的优化方式称之为流水线优化

此外,针对窄依赖,如果子RDD某个分区数据丢失,只需要找到父RDD对应依赖的分区,恢复即可。但如果是宽依赖,当分区丢失时,最糟糕的情况是要重算所有父RDD的所有分区。

3、宽依赖

对于gruopByKey这样的操作,子RDD的所有Partition(s)会依赖于parent RDD的所有Partition(s),子RDD的Partition是parent RDD的所有Partition Shuffle的结果

宽依赖的源码:

class ShuffleDependency[K, V, C](
    @transient _rdd: RDD[_ <: Product2[K, V]],
    val partitioner: Partitioner,
    val serializer: Option[Serializer] = None,
    val keyOrdering: Option[Ordering[K]] = None,
    val aggregator: Option[Aggregator[K, V, C]] = None,
    val mapSideCombine: Boolean = false)
extends Dependency[Product2[K, V]] {
 
override def rdd = _rdd.asInstanceOf[RDD[Product2[K, V]]]
//获取新的shuffleId
val shuffleId: Int = _rdd.context.newShuffleId()
//向ShuffleManager注册Shuffle的信息
val shuffleHandle: ShuffleHandle =
_rdd.context.env.shuffleManager.registerShuffle(
    shuffleId, _rdd.partitions.size, this)
 
    _rdd.sparkContext.cleaner.foreach(_.registerShuffleForCleanup(this))
}

宽依赖有Shuffle,底层会发生磁盘I/O ,以及生产Shuffle的临时文件,以及通过网络的fetch过程,所以从性能角度来看,产生Shuffle后,性能会有所降低。

4、Shuffle概述

Shuffle洗牌,在技术中,可以理解为按照某种分组条件,将数据分发到正确的分区,使得数据变得有规律。Spark底层产生Shuffle时,分区的机制:分组key.hashCode%分区数

spark中一旦遇到宽依赖就需要进行shuffle的操作,所谓的shuffle的操作的本质就是将数据汇总后重新分发的过程。

这个过程数据要汇总到一起,数据量可能很大所以不可避免的需要进行数据落磁盘的操作,会降低程序的性能,所以spark并不是完全内存不读写磁盘,只能说它尽力避免这样的过程来提高效率 。

spark中的shuffle,在早期的版本中,会产生多个临时文件,但是这种多临时文件的策略造成大量文件的同时的读写,磁盘的性能被分摊给多个文件,每个文件读写效率都不高,影响spark的执行效率。所以在后续的spark中(1.2.0之后的版本)的shuffle中,只会产生一个文件,并且数据会经过排序再附加索引信息,减少了文件的数量并通过排序索引的方式提升了性能。

三、DAG的生成与Stage的划分

1、DAG的生成

原始的RDD(s)通过一系列转换就形成了DAG。RDD之间的依赖关系,包含了RDD由哪些Parent RDD(s)转换而来和它依赖parent RDD(s)的哪些Partitions,是DAG的重要属性。

借助这些依赖关系,DAG可以认为这些RDD之间形成了Lineage(血统,血缘关系)。借助Lineage,能保证一个RDD被计算前,它所依赖的parent RDD都已经完成了计算;同时也实现了RDD的容错性,即如果一个RDD的部分或者全部的计算结果丢失了,那么就需要重新计算这部分丢失的数据。

RDD是分布式的,弹性的,容错的数据结构

2、Saprk的Stage(阶段)

Spark在执行任务(job)的时候,首先会根据依赖关系,将DAG划分为不同的阶段(Stage)。

处理流程是:

①、Spark在执行Transformation类型操作时都不会立即执行,而是懒执行(计算)

②、执行若干步的Transformation类型的操作后,一旦遇到Action类型操作时,才会真正触发执行(计算)

③、执行时,从当前Action方法向前回溯,如果遇到的是窄依赖则应用流水线优化,继续向前找,直到碰到某一个宽依赖

④、因为宽依赖必须要进行shuffle,无法实现优化,所以将这一次段执行过程组装为一个stage

⑤、再从当前宽依赖开始继续向前找。重复刚才的步骤,从而将整个DAG还分为若干的stage

在stage内部可以执行流水线优化,而在stage之间没办法执行流水线优化,因为有shuffle。但是这种机制已经尽力的去避免了shuffle。 

Stage阶段,本质是一组Task的集合

 简单记忆:一个DAG中有几个宽依赖,就会划分为几个Stage。

3、Saprk的Job和Task

原始的RDD经过一系列转换后(一个DAG),会在最后一个RDD上触发一个动作,这个动作会生成一个Job。

所以可以这样理解:一个DAG对应一个Spark的Job。

在Job被划分为一批计算任务(Task)后,这批Task会被提交到集群上的计算节点去计算

Spark的Task分为两种:

1)org.apache.spark.scheduler.ShuffleMapTask

2)org.apache.spark.scheduler.ResultTask

简单来说,DAG的最后一个阶段会为每个结果的Partition生成一个ResultTask,其余所有的阶段都会生成ShuffleMapTask。

4、可视化理解窄依赖和宽依赖

案例:单词统计

1)打开web页面控制台(hadoop01:4040端口地址),刷新,会发现刚才的操作会出现在页面上

 

刷新:

2)点击 Description下的 collect at…… 进入job的详细页面

3)点击 DAG Visualization 会出现如下图形

 

四、Spark框架核心概念

1.RDD: 弹性分布式数据集,是Spark最核心的数据结构。有分区机制,所以可以分布式进行处理。有容错机制,通过RDD之间的依赖关系来恢复数据。

2.依赖关系:RDD的依赖关系是通过各种Transformation(变换)来得到的。父RDD和子RDD之间的依赖关系分两种:①窄依赖  ②宽依赖

        ①针对窄依赖:父RDD的分区和子RDD的分区关系是:一对一

        窄依赖不会发生Shuffle,执行效率高,spark框架底层会针对多个连续的窄依赖执行流水线优化,从而提高性能。例如 map  flatMap等方法都是窄依赖方法

        ②针对宽依赖:父RDD的分区和子RDD的分区关系是:一对多

        宽依赖会产生shuffle,会产生磁盘读写,无法优化。

3.DAG:有向无环图,当一整条RDD的依赖关系形成之后,就形成了一个DAG。一般来说,一个DAG,最后都至少会触发一个Action操作,触发执行。一个Action对应一个Job任务。

4.Stage: 一个DAG会根据RDD之间的依赖关系进行Stage划分,流程是:以Action为基准,向前回溯,遇到宽依赖,就形成一个Stage。遇到窄依赖,则执行流水线优化(将多个连续的窄依赖放到一起执行)

5.task:任务。一个分区对应一个task。可以这样理解:一个Stage是一组Task的集合

6.RDD的Transformation(变换)操作:懒执行,并不会立即执行

7.RDD的Action(执行)操作:触发真正的执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是小先生

知识是无价的,白嫖也可以的。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值