“戏”说Spark-Spark核心-Stage划分及Pipline的计算模式

来源:互联网 发布:全国复杂网络大会 编辑:程序博客网 时间:2024/06/04 08:42
“戏”说Spark-Spark核心-Stage划分及Pipline的计算模式
RDD的依赖关系回顾
在“戏”说Spark-Spark核心-RDD依赖关系及源码解析一文中,我们总结了RDD之间的依赖关系分为宽依赖和窄依赖,划分宽依赖和窄依赖的依据总结为一句话就是:
如果父RDD的一个Partition被子RDD的一个Partition所使用就是窄依赖,否则的话就是宽依赖。
其中,我们也提到了Spark划分宽窄依赖的原因是:
1:narrow dependencies可以支持在同一个cluster node上以管道形式执行多条命令,例如在执行了map后,紧接着执行filter。相反,wide dependencies需要所有的父分区都是可用的,可能还需要调用类似MapReduce之类的操作进行跨节点传递。
2:则是从失败恢复的角度考虑。narrow dependencies的失败恢复更有效,因为它只需要重新计算丢失的parent partition即可,而且可以并行地在不同节点进行重计算。而wide dependencies牵涉到RDD各级的多个Parent Partitions。
总结一下就是因为:窄依赖的Pipline的计算模型能够带来更高的计算效率和更好的容错恢复
那什么是Pipline的计算模型呢?
Spark概念术语回顾
回顾下Spark中的专业术语:

现在从任务和资源两方面来回顾总结下Spark中的专业术语和概念,形成比较直观的体系:

Application任务的切分层次
Stage的划分
在spark中,会根据RDD之间的依赖关系将DAG图划分为不同的阶段,对于窄依赖,由于partition依赖关系的确定性,partition的转换处理就可以在同一个线程里完成,窄依赖就被spark划分到同一个stage中,而对于宽依赖,只能等父RDD shuffle处理完成后,下一个stage才能开始接下来的计算。
因此spark划分stage的整体思路是:从后往前推,遇到宽依赖就断开,划分为一个stage;遇到窄依赖就将这个RDD加入该stage中。因此在图2中RDD C,RDD D,RDD E,RDDF被构建在一个stage中,RDD A被构建在一个单独的Stage中,而RDD B和RDD G又被构建在同一个stage中。
在spark中,Task的类型分为2种:ShuffleMapTask和ResultTask;
简单来说,DAG的最后一个阶段会为每个结果的partition生成一个ResultTask(相当于shuffle Write阶段的task),即每个Stage里面的Task的数量是由该Stage中最后一个RDD的Partition的数量所决定的(jion的特殊情况及Pipline的递归函数的展开式决定的)!而其余所有阶段都会生成ShuffleMapTask;之所以称之为ShuffleMapTask是因为它需要将自己的计算结果通过shuffle到下一个stage中;也就是说图2中的stage1和stage2相当于mapreduce中的Mapper,而ResultTask所代表的stage3就相当于mapreduce中的reducer。
补充:DAG--有方向的无闭环的图结构
任务的层次划分
知道了任务的划分和资源的层次关系,Stage的划分后,我们知道一个Application应用程序分为Driver端和Executor端,Driver端负责任务的划分,Executor端负责任务的执行。而Application划分任务层次是根据Action算子将Application划分为jobs,在一个job里面从遇到Action算子的那个finalRDD开始从后往前回溯,遇到宽依赖的算子就划分出来一个Stage,Stage里面的RDD之间都是窄依赖的关系,窄依赖的父RDD和子RDD之间的的Partition是一对一的关系,每个Partition之间的转换就一个task任务。
注意:

Stage里面的pipline的计算模式
我们以WordCount例子来详解的讲解PiPline的计算模式:
WordCount伪代码--Scala版:

sc.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).foreach(println)

将其图文并茂为下图:

Stage中任务执行的内幕思考
  在一个stage内部,从表面上看是数据在不断流动,然后经过相应的算子处理后再流向下一个算子,但实质是算子在流动;我们可以从如下两个方面来理解:
  (1)  数据不动代码动;这点从算法构建和逻辑上来说,是算子作用于数据上,而算子处理数据一般有多个步骤,所以这里说数据不动代码动;
  (2) 在一个stage内部,算子之所以会流动(pipeline)首先是因为算子合并,也就是所谓的函数式编程在执行的时候最终进行函数的展开,从而把一个stage内部的多个算子合并成为一个大算子(其内部包含了当前stage中所有算子对数据的所有计算逻辑);其次是由于Transformation操作的Lazy特性。因为这些转换操作是Lazy的,所以才可以将这些算子合并;如果我们直接使用scala语言是不可以的,即使可以在算子前面加上一个Lazy关键字,但是它每次操作的时候都会产生中间结果。同时在具体算子交给集群的executor计算之前首先会通过Spark Framework(DAGScheduler)进行算子的优化(即基于数据本地性的pipeline)。

注意:
如何改变RDD的分区数?
reduceByKey(func,numPartitions)
groupByKey(func,numPartitions)
Stage0和Stage1之间是由宽依赖划分的,宽依赖的变现形式为具有宽依赖性质的算子,宽依赖算子影响的两个RDD之间会产生Shuffle,宽依赖之前的RDD的结果作为MapTask,宽依赖之后的RDD的结果作为ReduceTask
MapTask的数量是由Stage0中的最后一个RDD的分区数决定的,分区数决定了这个Stage0的并行度。宽依赖之后的RDD的分区数是由分区器决定的
总结
RDD的宽窄依赖的划分是为了划分Stage,划分Stage是为了Pipline计算模型的实现,Pipline的计算模式能够以一种管道流的方式,以高阶函数的形式实现数据的本地化,传逻辑而不传输数据。在Pipline计算模式遇到持久化算子或者Shuffle(宽依赖算子)时候就会产生数据的落地,Shuffle的write阶段由分区器决定将计算结果溢写到哪一个小文件。
关于Spark Stage的源码解析,请参考:
Stage生成和Stage源码浅析:
:http://www.jianshu.com/p/d3b794567e2a
Spark技术内幕:Stage划分及提交源码分析:
https://yq.aliyun.com/articles/2808
思维导图构建你的知识架构

参考:
spark 中如何划分stage?:
http://blog.csdn.net/shadow_mi/article/details/51821613
原创粉丝点击