Application(应用程序):即Spark应用,指的是一个可运行的Spark程序。该程序包含main()函数。同时,应用程序包含了一些配置参数,如需要占用的CPU个数,Executor内存大小等。
Driver(驱动程序):Spark中的Driver,指实际在运行上述Application的main()函数并且创建SparkContext
,其中创建SparkContext
的目的是为了准备Spark应用程序的运行环境。在Spark中由SparkContext
负责与Cluster Manager通信,进行资源申请,任务的分配和监控等。当Executor部分运行完毕后,Driver负责将SparkContext关闭。通常用SparkContext
代表Driver。
Cluster Manager(集群资源管理器):提供了资源的分配和管理,在不同的运行模式下,担任的角色有所不同。
Worker(工作节点):表示集群中任何可以运行Application代码的节点,类似于YAEN中的NodeManager节点。
Executor(执行进程):是Spark计算资源的一个单位,Spark先以Executor为单位占用集群资源,然后可以将具体的计算任务分配给Executor执行。Executor在物理机上是一个JVM进程,可以运行多个线程(计算任务)。
Application运行在worker节点上的一个进程,该进程负责运行Task,并负责将数据存在内存或磁盘上,每个Application都有各自独立的一批Executor。
CoarseGrainedExecutorBackend
,类似于Hadoop MapReduce中的YarnChild。CoarseGrainedExecutorBackend
进程有且仅有一个executor对象,他负责将Task包装成TaskRunner,并从线程池中抽取出一个空闲线程运行Task。CoarseGrainedExecutorBackend
能并行运行Task的数量就取决于分配给发他的CPU的个数。用户启动应用程序时,SparkContext会向Master发送应用注册消息,由Master给该应用程序分配Executor,Executor启动后Executor会向SparkContext发送注册成功消息。
DAGScheduler
进行划分Stage,并将Stage转换为TaskSet;TaskScheduler
向注册的Executor发送执行消息,Executor接收到任务消息后启动并运行;执行应用程序需要启动SparkContext,在SparkContext启动过程中会先实例化SchedulerBackend
对象,SchedulerBackend
负责应用程序运行期间与底层资源调度系统交互。
在Standalone模式中实际创建的是SparkDeploySchedulerBackend
对象,SparkDeploySchedulerBackend
继承至CoarseGraninedSchedulerBackend
。
该对象的启动中会继承父类的DriverEndPoint
和创建AppClient.ClientEndPoint
的两个终端点。在ClientEndPoint
的中创建注册线程池,在该线程池中启动注册线程并向Master发送RegisterApplication
注册应用程序的消息。
当Master接收到SparkContext
注册应用程序的消息后,记录应用程序信息并把应用程序加入到等待运行的应用程序列表中,注册完毕后发送成功消息RegisteredApplication
给ClientEndPoint
,同时调用startExecutorOnWorkers方法分配资源运行应用程序。在执行前需要获取运行应用程序的Worker,然后发送LaunchExecutor
消息给Worker,通过Worker启动Executor。
ClientEndPoint接收到master发送的RegisteredApplication
消息,需要把注册标识registered置为true,Master注册线程获取状态变化后,完成注册Application进程。
第二阶段
当worker收到master发送过来的LaunchExecutor
消息,先实例化ExecutorRunner
对象,在ExecutorRunner
启动中会创建进程生成器ProcessBuilder
,然后由该生成器创建CoarseGrainedExecutorBackend
对象,该对象是Executor运行的容器。最后worker发送ExecutorStateChanged
消息给master。Master接收到worker发送的ExecutorStateChanged
消息,根据ExecutorState向Driver
发送ExecutorUpdataed
消息。
上述在CoarseGrainedExecutorBackend
启动方法onStart中,会发送注册Executor消息RegisterExecutor
给DriverEndPoint
,DriverEndPoint
先判断Exector是否已注册,如果已经存在,则发送注册失败RegisteredExecutorFailed
消息,否则DriverEndPoint
会记录该Executor信息并发送注册成功RegisteredExecutor
消息,当CoarseGrainedExecutorBackend
接收到Executor注册成功的RegisterExecutor
消息时,在CoarseGrainedExecutorBackend
容器中实例化Executor
对象。启动完毕后,会定时向Driver发送心跳信息,等待接收从DriverEndPoint
发送执行任务的消息。
在makeOffers方法中分配运行任务资源,最后发送LaunchTask
消息执行任务。CoarseGrainedExecutorBackend
的Executor启动后,接收从DriverEndPoint
发送LaunchTask
执行任务消息,任务执行是在Executor
的lanuchTask方法实现的。在执行任务会创建TaskRunner
进程,由该进程进行任务的处理。在TaskRunner
执行任务完成时,会向DriverEndPoint
发送状态变更StatusUpdate
消息,当DriverEndPoint
接收到该消息时,调用TaskSchedulerImpl
的statusUpdata
方法,根据任务任务执行的不同的结果进行处理,处理完毕后再给该Executor
分配执行任务。
相关术语介绍:
DAGScheduler
进行解析。DAGScheduler
将DAG拆分成不同阶段的具有依赖关系的Stage,Stage有ShuffleMapStage
(上游的Stage)和ResultStage
(作业中最后的Stage)两种。ShuffleMapStage
中的Task为ShuffleMapTask
,而ResultStage中
的Task为ResultTask
。ShuffleMapTask
和ResultTask
类似于Hadoop中的Map任务和Reduce任务。DAGScheduler
是面向Stage的任务调度器,负责接收Spark应用提交的Job(DAG),根据RDD的依赖关系划分Stage,并提交Stage给TaskScheduler。
DAGScheduler
记录哪些RDD被存入磁盘等物化操作,同时要寻求任务的最优化调度;DAGScheduler
监控运行Stage过程,如果某个Stage运行失败,则需要重新提交该Stage。TaskScheduler
是面向Task的调度器,负责具体Task的调度执行。接收DAGScheduler
提交过来的Stage,然后把Stage以Task的形式一个个分发到Worker节点运行,由Worker节点的Executor来运行该任务。
TaskScheduler
只为一个SparkContext
实例服务。如果某个任务运行失败,TaskScheduler
要负责重试,另外,如果TaskScheduler
发下某个任务一直未运行完,就可能启动同样的任务运行同一个任务,哪个任务先运行完就用哪个任务的结果。TaskScheduler
是特质类,是DAGScheduler
与任务执行SchedulerBackend
的桥梁。TaskSchedulerImpl
是其最重要的子类,它实现了TaskScheduler所有的接口方法。TaskScheduler
的孙子类YarnSchedule
r和YarnClusterScheduler
只是重写了其中部分方法。YarnScheduler
和YarnClusterScheduler
在spark-yarn模块中。SchedulerBackend
负责应用程序运行期间与底层资源调度系统交互。该类为特质类。
子类根据不同运行模式分为:
org.apache.spark.scheduler.local.LocalBackend
org.apache.spark.scheduler.cluster.CoarseGrainedSchedulerBackend
org.apache.spark.scheduler.cluster.mesos.MesosSchedulerBackend
等。org.apache.spark.scheduler.cluster.SparkDeploySchedulerBackend
org.apache.spark.scheduler.cluster.YarnSchedulerBackend
org.apache.spark.scheduler.cluster.YarnClientSchedulerBackend
org.apache.spark.scheduler.cluster.YarnClusterSchedulerBackend
org.apache.spark.scheduler.cluster.mesos.CoarseMesosSchedulerBackend
等。上述所涉及的类是1.6.x
中的描述,在2.x
之后部分类有所变化,变化如下表所示,在2.x
版本开始使用新的类代替了旧的类
1.6.x及之前 | 2.0.0及之后 |
---|---|
LocalBackend | LocalSchedulerBackend |
CoarseGrainedSchedulerBackend | CoarseGrainedSchedulerBackend |
MesosSchedulerBackend | MesosFineGrainedSchedulerBackend,在2.1.0 将该类移至spark-mesos模块 |
SparkDeploySchedulerBackend | StandaloneSchedulerBackend |
YarnSchedulerBackend | YarnSchedulerBackend,在2.0.0 将该类移至spark-yarn模块 |
CoarseMesosSchedulerBackend | MesosCoarseGrainedSchedulerBackend,在2.1.0 将该类移至spark-mesos模块 |
DAGScheduler
进行解析。DAGScheduler
是面向Stage的任务调度器,把DAG根据RDD的依赖是否为宽依赖拆分成相互依赖的stage。每一个stage包括一个或多个task,这些任务形成任务集,并提交给底层调度器TaskScheduler
进行调度运行。另外DAGScheduler
记录哪些RDD被存入磁盘等物化操作,同时要寻求任务的最优化调度;DAGScheduler
监控运行Stage过程,如果某个Stage运行失败,则需要重新提交该Stage。TaskScheduler
只为一个SparkContext
实例服务,TaskScheduler
接收来自DAGScheduler
提交过来的stage,然后把Stage以Task的形式一个个分发到Worker节点的Executor中去运行任务。如果某个任务运行失败,TaskScheduler
要负责重试,另外,如果TaskScheduler
发下某个任务一直未运行完,就可能启动同样的任务运行同一个任务,哪个任务先运行完就用哪个任务的结果。TaskScheduler
发送过来的任务后,以多线程的方式运行,每一个线程负责一个任务。任务运行结束后要返回给TaskScheduler
,不同类型的任务,返回的方式也不同。ShuffleMapStage
返回的是一个MapStatus
对象,而不是结果本身;ResultStage
根据结果大小的不同,返回的方式又可以分为两类。应用程序运行过程是:在SparkContext启动时,调用TaskScheduler.start方法启动TaskScheduler调用器;然后,当DAGScheduler调度阶段和任务拆分完毕时,调用TaskScheduler.submitTask方法提交任务,SchedulerBackend接到执行任务时,通过reviveOffers方法分配运行资源并企启动运行节点的Executor;最后,由TaskScheduler接收任务运行状态,如果任务运行完成,则继续分配。
Job的真正提交是从action操作开始的,action操作会在内部隐性调用org.apache.spark.SparkContext#runJob方法。用户不用显示的去提交作业。
因此作业提交实际是通过org.apache.spark.SparkContext#runJob方法进行的。可以通过操作中是否包含runJob方法来判断该操作是否为action操作。
SparkContext的runJob方法经过几次调用后,进入了DAGScheduler的runJob方法,在runJob方法里,调用submitJob方法来继续提交作业,这里会发生阻塞,直到返回作业完成或者失败的结果,最后调用DAGScheduler的handleJobSubmitted方法来提交作业,在该方法中将进行划分stage。
spark stage的划分是由DAGScheduler实现的,DAGScheduler会从最后一个RDD出发使用广度优先遍历整个依赖数,从而划分stage,stage划分依据是以操作是否为宽依赖进行的,即当某个RDD的操作是shuffle时,以该shuffle操作为界限划分前后两个调度阶段。
代码实现是在org.apache.spark.scheduler.DAGScheduler#handleJobSubmitted方法中根据最后一个RDD生成ResultStage(作业中最后的stage)开始的。
调度划分阶段是spark作业执行的重要部分:
1.在SparkContext中提交运行时,会调用DAGScheduler#handleJobSubmitted方法进行处理,在该方法中会先找到最后一个RDD(即rddG),并调用getParentStage方法。
2.在getParentStages方法判断rddG的依赖树中是否存在shuffle操作,join为shuffle操作,则获取进进行该操作的rddB和rddF。
3.使用getAncestorShuffleDependencies方法从rddB向前遍历,发现该依赖分支上没有其他的宽依赖,调动newOrUsedShuffleStage方法生成调用阶段ShuffleMapStage0
4.使用getAncestorShuffleDependencies方法从rddF向前遍历,寻找该依赖分支存在宽依赖操作groupBy,以此为界划分rddD和rddE为ShuffleMapStage1,rddE和rddF为ShuffleMapStage2
5.最后生成rddG的ResultStage3。在划分调度阶段中,共划分4个stage
在DAGScheduler的handleJobSubmitted方法中,生成finalStage的同时建立起所有stage的依赖关系,然后通过finalStage生成一个作业实例,在该作业实例中按照顺序提交stage进行执行,在执行过程中通过监听总线获取作业、阶段执行情况。
1.在handleJobSubmitted方法中获取最后一个stage(ResultStage3),通过submitStage方法提交运行该stage。
2.在submitStage中,先创建作业实例,然后判断该stage是否存在父stage,有与ResultStage3有两个父Stage ShuffleMapStage0和ShuffleMapStage2,所以并不能立即提交父调度阶段运行,把ResultStage3加入到等待执行stage列表waitingStages中。
……
当stage提交运行后,在DAGScheduler的submitMissingTasks方法中,会根据stage partition个数拆分对应数量的任务,这些任务组成一个任务集提交到TaskScheduler进行分处理。对于ResultStage(作业中最后的stage)生成ResultTask,对于ShuffleMapStages生成ShuffleMap Task。对于每一个任务集包含了对应stage的所有任务,这些任务处理逻辑完全相同,不同的是对应处理的数据,而这些数据是其对应的partition。
当TaskScheduler接收到发送过来的任务集时,在submitTasks方法中(在TaskSchedulerImpl类中进行实现)构建一个TaskSetManager的实例,用于管理这个任务集的生命周期,而该TaskSetManager会放入系统的调度池中,根据系统设置的调度算法进行调度。
在TaskSchedulerImpl的resourceOffers方法中进行非常重要的步骤-资源分配,在分配的过程中会根据调度策略对TaskSetManager进行排序,然后依次对这些TaskSetManager按照就近原则分配资源,按照顺序为PROCESS_LOCAL、NODE_LOCAL、NO_PREF、RACK_LOCAL和ANY。
分配好资源的任务提交到CoarseGrainedExecutorBackend,然后通过其内部的Executor来执行任务。
当CoarseGrainedExecutorBackend接收到LaunchTask消息时,会调用Executor的launchTask方法进行处理。在Executor的launchTask方法中,初始化一个TaskRunner来封装任务,它用于管理任务运行的细节,再把TaskRunner对象放入到ThreadPool(线程池)中去执行。
在TaskRunner的run方法里,首先会对发送过来的Task本身以及它所依赖的jar等文件反序列化,然后对反序列化的任务调用Task的runTask方法。由于Task本身是一个抽象类,具体的runTask方法是由他的两个子类ShuffleMapTask和ResultTask来实现的。
对于ShuffleMapTask而言,他的计算结果会写到BlockManager之中,最终返回给DAGScheduler的是一个MapStatus对象。该对象中管理了ShuffleMapTask的运算结果存储到BlockManager里的相关存储信息,而不是自己算结果本身,这些存储信息将会成为下一阶段的任务需要获得的输入数据时的依据。
对于ResultTask的runTask而言,它最终返回的是func函数的计算结果。
……
UI监控分为实时UI监控和历史UI监控两种方式,默认情况下启用实时UI监控,历史UI监控需要手动启用。
实时UI监控分为Master UI监控和应用程序UI监控:
如果端口占用,会逐渐递增直至可用。
应用程序UI监控,一般包括作业,调度阶段,存储,运行环境,Executor和SQL等信息,如果启动了JDBC服务,则还会有JDBC/ODBC Server信息,在Spark Streaming中会增加Streaming监控信息。在Spark1.4版本中,UI监控增加了数据可视化功能,增加了事件时间轴,执行DAG和Spark Streaming统计3个视图。