Spark 核心原理

1. Spark核心原理

Spark运行结构图

  • 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(集群资源管理器):提供了资源的分配和管理,在不同的运行模式下,担任的角色有所不同。

    • Standalone:Spark原生的资源管理器,由Master负责资源的管理。
      Master:Spark Standalone运行模式下的主节点,负责管理和分配集群资源来运行Spark Application。
    • YARN:由YARN中的ResourceManager负责资源的管理。
    • Mesos:由Mesos中的Mesos Master负责资源的管理。
  • Worker(工作节点):表示集群中任何可以运行Application代码的节点,类似于YAEN中的NodeManager节点。

    • 在Standalone模式中指的就是通过slave文件配置的Worker节点,
    • 在Spark On Yarn模式中指的就是NodeManager节点。
  • Executor(执行进程):是Spark计算资源的一个单位,Spark先以Executor为单位占用集群资源,然后可以将具体的计算任务分配给Executor执行。Executor在物理机上是一个JVM进程,可以运行多个线程(计算任务)
    Application运行在worker节点上的一个进程,该进程负责运行Task,并负责将数据存在内存或磁盘上,每个Application都有各自独立的一批Executor

    • 在Spark On Yarn和Standalone模式下,其进程名为CoarseGrainedExecutorBackend,类似于Hadoop MapReduce中的YarnChild。
    • 一个CoarseGrainedExecutorBackend进程有且仅有一个executor对象,他负责将Task包装成TaskRunner,并从线程池中抽取出一个空闲线程运行Task
    • 每个CoarseGrainedExecutorBackend能并行运行Task的数量就取决于分配给发他的CPU的个数

2. 消息通信原理

2.1. Spark运行时消息通信

用户启动应用程序时,SparkContext会向Master发送应用注册消息,由Master给该应用程序分配Executor,Executor启动后Executor会向SparkContext发送注册成功消息。

  1. 当SparkContext的RDD触发action操作后,将创建RDD的DAG,通过DAGScheduler进行划分Stage,并将Stage转换为TaskSet;
  2. 接着由TaskScheduler向注册的Executor发送执行消息,Executor接收到任务消息后启动并运行;
  3. 最后当所有任务运行时,由Driver处理结果并回收资源。

Spark运行消息通信的交互过程
第一阶段

执行应用程序需要启动SparkContext,在SparkContext启动过程中会先实例化SchedulerBackend对象,SchedulerBackend负责应用程序运行期间与底层资源调度系统交互
在Standalone模式中实际创建的是SparkDeploySchedulerBackend对象,SparkDeploySchedulerBackend继承至CoarseGraninedSchedulerBackend
该对象的启动中会继承父类的DriverEndPoint和创建AppClient.ClientEndPoint的两个终端点。在ClientEndPoint的中创建注册线程池,在该线程池中启动注册线程并向Master发送RegisterApplication注册应用程序的消息。

当Master接收到SparkContext注册应用程序的消息后,记录应用程序信息并把应用程序加入到等待运行的应用程序列表中,注册完毕后发送成功消息RegisteredApplicationClientEndPoint,同时调用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消息RegisterExecutorDriverEndPointDriverEndPoint先判断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接收到该消息时,调用TaskSchedulerImplstatusUpdata方法,根据任务任务执行的不同的结果进行处理,处理完毕后再给该Executor分配执行任务。

2.2. 作业执行原理

相关术语介绍:

  • Job:RDD 中由action操作所生成一个Job,一个Job中包含一个或多个Stage
    action操作触发Job运行,提交之后根据RDD之间的依赖关系构建DAG图,DAG图提交给DAGScheduler进行解析。
  • Stage:每个Job会因为RDD之间的依赖关系拆分成多组任务集合,称为调度阶段,也叫做任务集(TaskSet)。每个Stage包含一个或多个Task
    通过DAGScheduler将DAG拆分成不同阶段的具有依赖关系的Stage,Stage有ShuffleMapStage(上游的Stage)和ResultStage(作业中最后的Stage)两种。
  • Task:分发到Executor上的工作任务,是Spark实际执行应用的最小单元。Task以线程的方式运行在Executor进程中,执行具体的计算任务,由于Executor可以配置多个CPU,而一个task一般使用一个CPU,因此当Executor具有多个CPU时,可以运行多个task。Executor的内存空间由多个task共享。
    每个Stage内都会按照RDD的Partition数量,创建多个Task。
    ShuffleMapStage中的Task为ShuffleMapTask,而ResultStage中的Task为ResultTaskShuffleMapTaskResultTask类似于Hadoop中的Map任务和Reduce任务。
  • DAGScheduler:DAGScheduler是面向Stage的任务调度器,负责接收Spark应用提交的Job(DAG),根据RDD的依赖关系划分Stage,并提交Stage给TaskScheduler
    • DAGScheduler记录哪些RDD被存入磁盘等物化操作,同时要寻求任务的最优化调度;
    • DAGScheduler监控运行Stage过程,如果某个Stage运行失败,则需要重新提交该Stage。
  • TaskScheduler:TaskScheduler是面向Task的调度器,负责具体Task的调度执行。接收DAGScheduler提交过来的Stage,然后把Stage以Task的形式一个个分发到Worker节点运行,由Worker节点的Executor来运行该任务。
    • 每个TaskScheduler只为一个SparkContext实例服务。如果某个任务运行失败,TaskScheduler要负责重试,另外,如果TaskScheduler发下某个任务一直未运行完,就可能启动同样的任务运行同一个任务,哪个任务先运行完就用哪个任务的结果。
    • TaskScheduler是特质类,是DAGScheduler与任务执行SchedulerBackend的桥梁。TaskSchedulerImpl是其最重要的子类,它实现了TaskScheduler所有的接口方法。
      TaskScheduler的孙子类YarnScheduler和YarnClusterScheduler只是重写了其中部分方法。YarnSchedulerYarnClusterScheduler在spark-yarn模块中。

SchedulerBackend负责应用程序运行期间与底层资源调度系统交互。该类为特质类。
子类根据不同运行模式分为:

  • 本地运行模式的org.apache.spark.scheduler.local.LocalBackend
  • 粗粒度运行模式的org.apache.spark.scheduler.cluster.CoarseGrainedSchedulerBackend
  • 细粒度Mesos运行模式的org.apache.spark.scheduler.cluster.mesos.MesosSchedulerBackend等。
    粗粒度运行模式包括:
    • 独立运行模式的org.apache.spark.scheduler.cluster.SparkDeploySchedulerBackend
    • YARN运行模式的org.apache.spark.scheduler.cluster.YarnSchedulerBackend
      YARN运行模式根据SparkContext运行位置不同分为:
      • Yarn-Client运行模式的org.apache.spark.scheduler.cluster.YarnClientSchedulerBackend
      • Yarn-Cluster运行模式的org.apache.spark.scheduler.cluster.YarnClusterSchedulerBackend
        其中`YarnClientSchedulerBackend和YarnClusterSchedulerBackend在spark-yarn模块中。
    • Mesos运行模式的org.apache.spark.scheduler.cluster.mesos.CoarseMesosSchedulerBackend等。

上述所涉及的类是1.6.x中的描述,在2.x之后部分类有所变化,变化如下表所示,在2.x版本开始使用新的类代替了旧的类

1.6.x及之前2.0.0及之后
LocalBackendLocalSchedulerBackend
CoarseGrainedSchedulerBackendCoarseGrainedSchedulerBackend
MesosSchedulerBackendMesosFineGrainedSchedulerBackend,在2.1.0将该类移至spark-mesos模块
SparkDeploySchedulerBackendStandaloneSchedulerBackend
YarnSchedulerBackendYarnSchedulerBackend,在2.0.0将该类移至spark-yarn模块
CoarseMesosSchedulerBackendMesosCoarseGrainedSchedulerBackend,在2.1.0将该类移至spark-mesos模块

2.2.1. 总述

  1. Spark应用程序进行各种转换操作,通过active操作触发作业运行。提交之后根据RDD之间的依赖关系构建DAG图,DAG图提交给DAGScheduler进行解析。
  2. DAGScheduler是面向Stage的任务调度器,把DAG根据RDD的依赖是否为宽依赖拆分成相互依赖的stage。每一个stage包括一个或多个task,这些任务形成任务集,并提交给底层调度器TaskScheduler进行调度运行。另外DAGScheduler记录哪些RDD被存入磁盘等物化操作,同时要寻求任务的最优化调度;DAGScheduler监控运行Stage过程,如果某个Stage运行失败,则需要重新提交该Stage。
  3. 每个TaskScheduler只为一个SparkContext实例服务,TaskScheduler接收来自DAGScheduler提交过来的stage,然后把Stage以Task的形式一个个分发到Worker节点的Executor中去运行任务。如果某个任务运行失败,TaskScheduler要负责重试,另外,如果TaskScheduler发下某个任务一直未运行完,就可能启动同样的任务运行同一个任务,哪个任务先运行完就用哪个任务的结果。
  4. worker中的Executor收到TaskScheduler发送过来的任务后,以多线程的方式运行,每一个线程负责一个任务。任务运行结束后要返回给TaskScheduler,不同类型的任务,返回的方式也不同。ShuffleMapStage返回的是一个MapStatus对象,而不是结果本身;ResultStage根据结果大小的不同,返回的方式又可以分为两类。

应用程序运行过程是:在SparkContext启动时,调用TaskScheduler.start方法启动TaskScheduler调用器;然后,当DAGScheduler调度阶段和任务拆分完毕时,调用TaskScheduler.submitTask方法提交任务,SchedulerBackend接到执行任务时,通过reviveOffers方法分配运行资源并企启动运行节点的Executor;最后,由TaskScheduler接收任务运行状态,如果任务运行完成,则继续分配。
Spark的作业和任务调度系统

2.2.2. 提交Job

Job的真正提交是从action操作开始的,action操作会在内部隐性调用org.apache.spark.SparkContext#runJob方法。用户不用显示的去提交作业。
因此作业提交实际是通过org.apache.spark.SparkContext#runJob方法进行的。可以通过操作中是否包含runJob方法来判断该操作是否为action操作。

SparkContext的runJob方法经过几次调用后,进入了DAGScheduler的runJob方法,在runJob方法里,调用submitJob方法来继续提交作业,这里会发生阻塞,直到返回作业完成或者失败的结果,最后调用DAGScheduler的handleJobSubmitted方法来提交作业,在该方法中将进行划分stage。

2.2.3. 划分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

2.2.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中。

……

2.2.5. 提交task

当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来执行任务。

2.2.6. 执行task

当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函数的计算结果。

2.2.7. 获取执行结果

……

3. 监控管理

3.1. UI监控

UI监控分为实时UI监控历史UI监控两种方式,默认情况下启用实时UI监控,历史UI监控需要手动启用。
实时UI监控分为Master UI监控应用程序UI监控

  • Master UI监控默认使用8080端口,Spark standalone模式下的Master监控,在Master启动过程中启用;
  • 应用程序UI监控默认使用4040端口,应用程序UI监控在SparkContext启用。

如果端口占用,会逐渐递增直至可用。

应用程序UI监控,一般包括作业,调度阶段,存储,运行环境,Executor和SQL等信息,如果启动了JDBC服务,则还会有JDBC/ODBC Server信息,在Spark Streaming中会增加Streaming监控信息。在Spark1.4版本中,UI监控增加了数据可视化功能,增加了事件时间轴,执行DAG和Spark Streaming统计3个视图。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值