Spark任务提交方式和执行流程
转自:http://www.cnblogs.com/frankdeng/p/9301485.html
一、Spark集群模式概述
Spark 应用在集群上作为独立的进程组来运行,在您的main程序中通过SparkContext来协调(称之为driver程序)。
一、Spark中的基本概念
(1)Application:表示你的应用程序
(2)Driver:表示main()函数,创建SparkContext。由SparkContext负责与ClusterManager通信,进行资源的申请,任务的分配和监控等。程序执行完毕后关闭SparkContext
(3)Executor:某个Application运行在Worker节点上的一个进程,该进程负责运行某些task,并且负责将数据存在内存或者磁盘上。在Spark on Yarn模式下,其进程名称为 CoarseGrainedExecutor Backend,一个CoarseGrainedExecutor Backend进程有且仅有一个executor对象,它负责将Task包装成taskRunner,并从线程池中抽取出一个空闲线程运行Task,这样,每个CoarseGrainedExecutorBackend能并行运行Task的数据就取决于分配给它的CPU的个数。
(4)Worker:集群中可以运行Application代码的节点。在Standalone模式中指的是通过slave文件配置的worker节点,在Spark on Yarn模式中指的就是NodeManager节点。
(5)Task:在Executor进程中执行任务的工作单元,多个Task组成一个Stage
(6)Job:包含多个Task组成的并行计算,是由Action行为触发的
(7)Stage:每个Job会被拆分很多组Task,作为一个TaskSet,其名称为Stage
(8)DAGScheduler:根据Job构建基于Stage的DAG,并提交Stage给TaskScheduler,其划分Stage的依据是RDD之间的依赖关系
(9)TaskScheduler:将TaskSet提交给Worker(集群)运行,每个Executor运行什么Task就是在此处分配的。
二、Spark的运行流程
2.1 Spark的基本运行流程
1、说明
(1)构建Spark Application的运行环境(启动SparkContext),SparkContext向资源管理器(可以是Standalone、Mesos或YARN)注册并申请运行Executor资源;
(2)资源管理器分配Executor资源并启动StandaloneExecutorBackend,Executor运行情况将随着心跳发送到资源管理器上;
(3)SparkContext构建成DAG图,将DAG图分解成Stage,并把Taskset发送给Task Scheduler。Executor向SparkContext申请Task
(4)Task Scheduler将Task发放给Executor运行同时SparkContext将应用程序代码发放给Executor。
(5)Task在Executor上运行,运行完毕释放所有资源。
2、图解
3、Spark运行架构特点
(1)每个Application获取专属的executor进程,该进程在Application期间一直驻留,并以多线程方式运行tasks。这种Application隔离机制有其优势的,无论是从调度角度看(每个Driver调度它自己的任务),还是从运行角度看(来自不同Application的Task运行在不同的JVM中)。当然,这也意味着Spark Application不能跨应用程序共享数据,除非将数据写入到外部存储系统。
(2)Spark与资源管理器无关,只要能够获取executor进程,并能保持相互通信就可以了。
(3)提交SparkContext的Client应该靠近Worker节点(运行Executor的节点),最好是在同一个Rack里,因为Spark Application运行过程中SparkContext和Executor之间有大量的信息交换;如果想在远程集群中运行,最好使用RPC将SparkContext提交给集群,不要远离Worker运行SparkContext。
(4)Task采用了数据本地性和推测执行的优化机制。
4、DAGScheduler
Job=多个stage
Stage=多个同种task,
Task分为ShuffleMapTask和ResultTask
Dependency分为ShuffleDependency和NarrowDependency
面向stage的切分,切分依据为宽依赖维护waiting jobs和active jobs,维护waiting stages、active stages和failed stages,以及与jobs的映射关系
主要职能:
1、接收提交Job的主入口,
submitJob(rdd, ...)
或runJob(rdd, ...)
。在SparkContext
里会调用这两个方法。
- 生成一个Stage并提交,接着判断Stage是否有父Stage未完成,若有,提交并等待父Stage,以此类推。结果是:DAGScheduler里增加了一些waiting stage和一个running stage。
- running stage提交后,分析stage里Task的类型,生成一个Task描述,即TaskSet。
- 调用
TaskScheduler.submitTask(taskSet, ...)
方法,把Task描述提交给TaskScheduler。TaskScheduler依据资源量和触发分配条件,会为这个TaskSet分配资源并触发执行。DAGScheduler
提交job后,异步返回JobWaiter
对象,能够返回job运行状态,能够cancel job,执行成功后会处理并返回结果2、处理
TaskCompletionEvent
- 如果task执行成功,对应的stage里减去这个task,做一些计数工作:
- 如果task是ResultTask,计数器
Accumulator
加一,在job里为该task置true,job finish总数加一。加完后如果finish数目与partition数目相等,说明这个stage完成了,标记stage完成,从running stages里减去这个stage,做一些stage移除的清理工作- 如果task是ShuffleMapTask,计数器
Accumulator
加一,在stage里加上一个output location,里面是一个MapStatus
类。MapStatus
是ShuffleMapTask
执行完成的返回,包含location信息和block size(可以选择压缩或未压缩)。同时检查该stage完成,向MapOutputTracker
注册本stage里的shuffleId和location信息。然后检查stage的output location里是否存在空,若存在空,说明一些task失败了,整个stage重新提交;否则,继续从waiting stages里提交下一个需要做的stage- 如果task是重提交,对应的stage里增加这个task
- 如果task是fetch失败,马上标记对应的stage完成,从running stages里减去。如果不允许retry,abort整个stage;否则,重新提交整个stage。另外,把这个fetch相关的location和map任务信息,从stage里剔除,从
MapOutputTracker
注销掉。最后,如果这次fetch的blockManagerId对象不为空,做一次ExecutorLost
处理,下次shuffle会换在另一个executor上去执行。- 其他task状态会由
TaskScheduler
处理,如Exception, TaskResultLost, commitDenied等。3、其他与job相关的操作还包括:cancel job, cancel stage, resubmit failed stage等
其他职能:
cacheLocations 和 preferLocation
5、TaskScheduler
维护task和executor对应关系,executor和物理资源对应关系,在排队的task和正在跑的task。
内部维护一个任务队列,根据FIFO或Fair策略,调度任务。
TaskScheduler
本身是个接口,spark里只实现了一个TaskSchedulerImpl
,理论上任务调度可以定制。
1、submitTasks(taskSet)
,接收DAGScheduler
提交来的tasks
- 为tasks创建一个
TaskSetManager
,添加到任务队列里。TaskSetManager
跟踪每个task的执行状况,维护了task的许多具体信息。 - 触发一次资源的索要。
- 首先,
TaskScheduler
对照手头的可用资源和Task队列,进行executor分配(考虑优先级、本地化等策略),符合条件的executor会被分配给TaskSetManager
。 - 然后,得到的Task描述交给
SchedulerBackend
,调用launchTask(tasks)
,触发executor上task的执行。task描述被序列化后发给executor,executor提取task信息,调用task的run()
方法执行计算。
- 首先,
2、cancelTasks(stageId)
,取消一个stage的tasks
- 调用
SchedulerBackend
的killTask(taskId, executorId, ...)
方法。taskId和executorId在TaskScheduler
里一直维护着。
3、resourceOffer(offers: Seq[Workers])
,这是非常重要的一个方法,调用者是SchedulerBacnend
,用途是底层资源SchedulerBackend
把空余的workers资源交给TaskScheduler
,让其根据调度策略为排队的任务分配合理的cpu和内存资源,然后把任务描述列表传回给SchedulerBackend
- 从worker offers里,搜集executor和host的对应关系、active executors、机架信息等等
- worker offers资源列表进行随机洗牌,任务队列里的任务列表依据调度策略进行一次排序
- 遍历每个taskSet,按照进程本地化、worker本地化、机器本地化、机架本地化的优先级顺序,为每个taskSet提供可用的cpu核数,看是否满足
- 默认一个task需要一个cpu,设置参数为
"spark.task.cpus=1"
- 为taskSet分配资源,校验是否满足的逻辑,最终在
TaskSetManager
的resourceOffer(execId, host, maxLocality)
方法里 - 满足的话,会生成最终的任务描述,并且调用
DAGScheduler
的taskStarted(task, info)
方法,通知DAGScheduler
,这时候每次会触发DAGScheduler
做一次submitMissingStage
的尝试,即stage的tasks都分配到了资源的话,马上会被提交执行
- 默认一个task需要一个cpu,设置参数为
4、statusUpdate(taskId, taskState, data)
,另一个非常重要的方法,调用者是SchedulerBacnend
,用途是SchedulerBacnend
会将task执行的状态汇报给TaskScheduler
做一些决定
- 若
TaskLost
,找到该task对应的executor,从active executor里移除,避免这个executor被分配到其他task继续失败下去。 - task finish包括四种状态:finished, killed, failed, lost。只有finished是成功执行完成了。其他三种是失败。
- task成功执行完,调用
TaskResultGetter.enqueueSuccessfulTask(taskSet, tid, data)
,否则调用TaskResultGetter.enqueueFailedTask(taskSet, tid, state, data)
。TaskResultGetter
内部维护了一个线程池,负责异步fetch task执行结果并反序列化。默认开四个线程做这件事,可配参数"spark.resultGetter.threads"=4
。
TaskResultGetter取task result的逻辑
1、对于success task,如果taskResult里的数据是直接结果数据,直接把data反序列出来得到结果;如果不是,会调用blockManager.getRemoteBytes(blockId)
从远程获取。如果远程取回的数据是空的,那么会调用TaskScheduler.handleFailedTask
,告诉它这个任务是完成了的但是数据是丢失的。否则,取到数据之后会通知BlockManagerMaster
移除这个block信息,调用TaskScheduler.handleSuccessfulTask
,告诉它这个任务是执行成功的,并且把result data传回去。
2、对于failed task,从data里解析出fail的理由,调用TaskScheduler.handleFailedTask
,告诉它这个任务失败了,理由是什么。
6、SchedulerBackend
在TaskScheduler
下层,用于对接不同的资源管理系统,SchedulerBackend
是个接口,需要实现的主要方法如下:
def start(): Unit
def stop(): Unit
def reviveOffers(): Unit // 重要方法:SchedulerBackend把自己手头上的可用资源交给TaskScheduler,TaskScheduler根据调度策略分配给排队的任务吗,返回一批可执行的任务描述,SchedulerBackend负责launchTask,即最终把task塞到了executor模型上,executor里的线程池会执行task的run()
def killTask(taskId: Long, executorId: String, interruptThread: Boolean): Unit =
throw new UnsupportedOperationException
粗粒度:进程常驻的模式,典型代表是standalone模式,mesos粗粒度模式,yarn
细粒度:mesos细粒度模式
这里讨论粗粒度模式,更好理解:CoarseGrainedSchedulerBackend
。
维护executor相关信息(包括executor的地址、通信端口、host、总核数,剩余核数),手头上executor有多少被注册使用了,有多少剩余,总共还有多少核是空的等等。