背景
执行流程
解释standalone执行原理可以抛开Driver和Client。
首先,简单说明下Master、Worker、App三种角色。
Application:带有自己需要的mem和cpu资源量,会在master里排队,最后被分发到worker上执行。app的启动是去各个worker遍历,获取可用的cpu,然后去各个worker launch executor。
Worker:每台slave起一个(也可以起多个),默认或被设置cpu和mem数,并在内存里做加减维护资源剩余量。Worker同时负责拉起本地的executor backend,即执行进程。
Master:接受Worker、app的注册,为app执行资源分配。Master和Worker本质上都是一个带Actor的进程。
接下来分析下图中的四个步骤。
第一步,register worker是一个启动集群和搜集初始资源的过程。在standalone模式下,预先在机器上使用脚本start master和slave。在这个过程中,worker的启动cpu和内存是设置好的,起来后把自己注册给master,从而master维护worker上的资源量和worker本身host、port等的信息。master的HA抛开不谈。
第二步,master接收新app的注册。app也好,driver也好,都是通过输入一个spark url提交的,最终在master内存里排队。当master有新的app进来,或资源可用性发生变化时,会触发资源分配的逻辑:
首先,将可用的alive workers进行洗牌打乱,遍历等待的drivers,为每个driver轮询遍历alive workers,只要worker的剩余mem和cpu满足该driver,那么就向那个worker通过actor sender发送一个LaunchDriver的消息,里面会包含driver的信息。
接着,遍历所有的waiting apps,同样为每个app遍历可用的worker,为其分配cpu。默认是spread out的策略,即一个app的cpus可以分布在不同的worker上。app会添加自己的executor,然后向Worker的actor传递LaunchExecutor的消息,并传递给这个app的driver一个ExecutorAdded的消息。
第三步,launch executor是一个重点。master在资源分配的逻辑里,为app分配了落在若干worker上的executors,然后对于每一个executor,master都会通知其worker去启动。standalone模式下,每个worker通过command命令行的方式启动CoarseGrainedExecutorBackend。CoarseGrainedExecutorBackend本质上也是一个Actor,里面最重要的是有一个线程池,可以执行真正的task。所以CoarseGrainedExecutorBackend具备了launchTask,killTask等方法,其TaskRunner的run()方法,调用的就是ShuffleMapTask或ResultTask的run()逻辑。
第四步,app自己来launch task。上面三步都是集群资源的准备过程,在这个过程里,app得到了属于自己的资源,包括cpu、内存、起起来的进程及其分布。在我看来,前三个过程是面向资源的调度过程,接在mesos、yarn上也可以,而第四个过程则是面向摆放的。App内的TaskScheduler和SchedulerBackend是我们熟悉的与task切分、task分配、task管理相关的内容。在之前spark任务调度的文章里也啰啰嗦嗦讲了一些。
模型分层
spark在这一块的设计是优秀的。图中,app内的SchedulerBackend是可以针对不同资源管理系统实现的,包括没有画出来的ExecutorBackend。这俩兄弟是典型的面向资源的层次上的抽象。另一方面,app内的TaskScheduler是与Task的分配和执行、管理相关的,这部分与下层面向资源的部分是隔离开的,所谓是面向摆放的。
换句话说,SchedulerBackend在1,2,3步之后,已经从集群里,获得了本身app的executors资源。通过它,TaskScheduler可以根据自己的策略,把Task与Executor对应起来,启动起来,管理起来。原本,TaskScheduler是个逻辑上的任务调度者,加上SchedulerBackend之后,其具备了操纵实际物理资源的能力,当然主要指的就是task locality与task在进程上的start和kill。