Spark(1.0)内核解析
Spark的内核部分主要从以下几个方面介绍:
任务调度系统、I/0模块、通信控制模块、容错模块、shuffle模块
一、任务调度系统
1、作业执行流程
接下来注意几个概念:
Application:用户自定义的Spark程序,用户提交后,Spark为App分配资源,将程序转换并执行。
Driver Program:运行Application的main()函数并创建SparkContext
RDD Graph:RDD是Spark的核心结构,可以通过一系列算子进行操作(主要有Transformation和Action操作)。当RDD遇到Action算子时,将之前的所有算子形成一个有向无环图(DAG),再在Spark中转化为Job,提交到集群执行。一个App中可以包含多个Job。
Job:一个RDD Graph触发的作业,往往由Spark Action算子触发,在SparkContext中通过runJob方法向Spark提交Job。
Stage:每个Job会根据RDD的宽依赖关系被切分成很多Stage,每个Stage中包含一组相同的Task,叫TaskSet
Task:一个分区对应一个Task,Task执行RDD中对应Stage中包含的算子,Task封装好之后放入Executor的线程池中执行。
作业执行流程:
1)用户启动客户端,之后客户端运行用户程序,启动Driver进程。在Driver中启动或实例化DAGScheduler等组件。客户端的Driver向Master注册。
2)Worker向Master注册(要确保master有活节点可用,或通过心跳机制向master汇报worker节点还活着),Master命令Worker启动Exeuctor。Worker通过创建ExecutorRunner线程,在ExecutorRunner线程内部启动ExecutorBackend进程。
3)ExecutorBackend启动后,向客户端Driver进程内的SchedulerBackend注册。而SchedulerBackend收到后,会向Worker启动LaunchTask进程,Worker开始执行任务。
4)Driver中的SchedulerBackend进程中包含DAGScheduler进程、TaskScheduler进程。SchedulerBackend收到注册后,DAGScheduler会根据RDD DAG切分成相应的Stage,每个Stage中包含的TaskSet通过TaskScheduler分配给Executor。Executor启动线程池并行化执行Task。
2、Spark的任务调度系统
1)在最左边通过一系列的Transformation算子和Actions算子,形成DAG图,传递给DAGScheduler,
2)然后DAGScheduler把其切分成一系列的Stage,即形成TaskSet任务集合,每个TaskSet中包含多个任务。然后把TaskSet传递给TaskScheduler
3)TaskScheduler收到后,然后把具体任务分配给相应的Worker节点进行计算。
3、以wordcount为例,解析触发Job全生命周期过程
sc.textFile(hdfs://....).flatMap(line=>line.split(" ")).map(word=>(word,1)).reduceByKey(_+_).saveAsFile(hdfs://...) 化简为: sc.textFile(hdfs://....).flatMap(_.split(" ")).map(_,1).reduceByKey(_+_).saveAsFile(hdfs://...) 因为默认得出的结果是没有序的,所以想要得到排序的结果: sc.textFile(hdfs://....).flatMap(_.split(" ")).map(_,1).reduceByKey(_+_).map(x=>(x._2,x._1)).sortByKey(false).map(x=>(x._2,x._1)).saveAsFile(hdfs://...)
上述代码经过了HadoopRDD-->MappedRDD-->flatMappedRDD-->MappedRDD-->PairRDDFunctions-->ShuffledRDD-->MappartitionsRDD-->MappedRDD-->SortedRDD-->MappedRDD-->HadoopRDD
HadoopRDD-->MappedRDD: sc.textFile(hdfs://....) MappedRDD-->flatMappedRDD: flatMap(_.split(" ")) flatMappedRDD-->MappedRDD: flatMap(_.split(" ")).map(_,1) MappedRDD-->PairRDDFunctions-->ShuffledRD: map(_,1).reduceByKey(_+_) ShuffledRDD-->MappartitionsRDD-->MappedRDD:reduceByKey(_+_).map(x=>(x._2,x._1)) MappedRDD-->SortedRDD: map(x=>(x._2,x._1)).sortByKey(false) SortedRDD-->MappedRDD: sortByKey(false).map(x=>(x._2,x._1)) MappedRDD-->HadoopRDD: map(x=>(x._2,x._1)).saveAsFile(hdfs://...)
4、流程总结
1、首先应用程序创建SparkContext的实例,如sc
2、利用SparkContext实例创建生成RDD
3、经过一连串的transforation操作,原始的RDD转换成其他类型的RDD
4、当action作用于转换之后的RDD时,会调用SparkContext的runJob方法
5、sc.runJob的调用是后面一连串反应的起点,关键性的跃变就发生在此处
6、sc.runJob-->DAGScheduler.runJob-->submitJob
7、DAGScheduler::submitJob会创建JobSummitted的event发送给内嵌类eventProcessActor
8、eventProcessActor在接收到JobSubmmitted之后调用processEvent处理函数
9、job到stage的转换,生成finalStage并提交运行,关键是调用submitStage
10、在submitStage中会计算stage之间的依赖(分两种,宽依赖和窄依赖)
11、如果计算中发现当前的stage没有任何依赖或所有依赖准备完毕,则提交stage
12、提交task是调用函数submitMissingTasks来完成
13、task真正运行在哪个worker是由TaskSecheduler来管理,也就是上面的submitMissingTasks会调用TaskScheduler::submitTasks
14、TaskSchedulerImpl中会根据Spark的当前运行模式来创建相应的backend,如果在单机运行则创建LocalBackend
15、LocalBackend收到TaskSchedulerImpl传递过来的ReceiveOffers事件
16、receiveOffers-->executor.launchTask-->TaskRunner.run
二、I/O模块
整体的I/O管理分为两个层次:
1)通信层:I/O模块也是采用Master-Slave结构来实现通信层的架构,Master和Slave之间传输控制信息和状态信息。
2)存储层:Spark的块数据需要存储到内存或磁盘,有可能还需传输到远端机器,这些是由存储层完成的。
RDD逻辑上是按照Partition分块的,可以将RDD看成一个分区作为数据项的分布式数组。在物理上RDD是以Block为单位,一个Partition对应一个Block,用Partition的ID通过元数据的映射到物理上的Block,而这个物理上的Block可以存储在内存,也可以存储在某个节点的Spark的硬盘临时目录。
当其他模块与stroage模块交互时,BlockManager来具体实现。
三、容错机制
容错有两种方式:
数据检查点(checkpoint机制)、记录数据的更新(Lineage机制)
1、Lineage机制(粗粒度转换)
原理:如果一个节点死了,而且运算Narrow Dependecy,则只需把丢失的父RDD分区重新计算即可,不依赖其他节点,不存在冗余计算。而Shuffle Dependecy需要父RDD的所有分区都存在,重算就很昂贵了(可采用Checkpoint算子来做检查点)。
2、Checkpoint机制
在以下两种情况需要做检查点:
1)DAG中的Lineage过长,如果重算,则开销太大(如PageRank)
2)在Shuffle Dependecy上做检查点获得的收益更大。
RDD采用同步方式做检查点,具体使用Synchronized保证方法的同步和线程安全
四、Shuffle机制
Shuffle的本意是洗牌、混洗。即把一定规则的数据打乱,而这里的Shuffle是其反过程,把一组无规则的数据换成有规则的数据。Shuffle分为两个阶段:
1)Shuffle Write:
Spark之分为两种任务,ShuffleMapTask和ResultTask。除了最后阶段将数据输出到Spark执行空间Stage这个阶段执行ResultTask,其余阶段都是执行ShuffleMapTask任务。
通过ShuffleMapTask中的runTask方法进入Shuffle Writer骨架。Spark支持两种流程:Shuffle和优化的Consolidate Shuffle
Consolidation Shuffle显著减少了shuffle文件的数量,解决了文件数量过多的问题,但是Writer Handler的Buffer开销过大依然没有减少。
2)Shuffle Fetch
Shuffle write阶段写到各个节点的数据,Reducer端的节点通过拉取数据而获取需要的数据,在Spark中叫Fetch。有两种方式:NIO通过Socket连接去fetch数据、OIO通过Netty去Fetch数据.