Spark2.x资源调度源码再析之大白话系列

spark采用的粗粒度资源申请方式决定了每一个task在执行前不需要自己去申请资源,而是使用Application执行前都已经申请完毕的资源。
那么资源调度的底层是如何实现的呢?
当start-all.sh 的时候启动了两个脚本
一个是start-master.sh 脚本中调用了org.apache.spark.deploy.master.Master这个类
一个是start-slaves.sh 脚本中调用了org.apache.spark.deploy.worker.Worker这个类
首先看下以standalone的cluster模式为例,具体启动流程
1、在客户端提交application,首先客户端会先向Master申请启动Driver
2、Master随机在一台Worker节点上启动Driver
3、Driver启动后向Master申请资源,Master返回资源
4、Driver发送Task,监控Task,回收结果

当master和worker都启动后,worker节点会找master节点进行汇报资源,汇报时在源码里面体现为 :
val workers = HashSet[workerInfo] workInfo中的信息记录的是worker中有多少和核以及有多少内存,汇报的时候把worker的这些信息封装在Master类中的HashSet集合中

val waitingApps = new ArrayBuffer[ApplicationInfo] 提交application的时候 ,当前application需要多少核以及需要多少内存,封装在ArrayBuffer这个数组结构里面

val waitingDrivers = new HashSet[DriverInfo] 启动Driver的时候在源码中体现为启动需要多少核以及需要多少内存,把这些信息封装在HashSet集合中

即通过rpc调用,把worker的信息封装在以上的数据结构中并存储在Master类中

一、在第2步中,客户端提交application后 开始向Master申请启动Driver
当Master和Worker都启动后,需要找到一个Worker去启动Driver,遍历所有Worker,判断Worker的内存和core是否大于启动Driver启动所需要的内存和核数 (standalone cluster模式默认分配Driver1g内存和1个核,如果某个work满足条件,然后work.addDriver(driver)(即在这个worker节点中启动driver进程)

二、在第3步中,Master返回资源即是在Worker上启动Executor的过程(详细讲解)

waitingApps代表的是所有向master注册的作业,首先遍历waitingAPPs,过滤出集群中所有满足当前Application要求core和memory的Worker的信息,即获得可用的Worker,然后获得每个Worker上可以为当Application奉献的core,下面就开始在Worker上调度Executor

方法:schedulerExecutorOnWorkers(app,usableWorkers,spreadOutApps)
app:即当前application
usableWorkers:过滤出的资源可用的Worker
spreadOutApps:传进来的是个true

两个重要的数组:
一个是val assignedCores = new Array[Int](numUsable)
一个是 val assignedExecutor = new Array[Int](numUsable)

一个worker会否能启动多个executor的判断方法 需要判断这个worker中剩余的core数和剩余的内存数是否满足启动一个executor的需求

总的意思就是说:不停的遍历所有的work 在数组的索引中分为0号worker 1号worker 2号worker … s

没有指定executor-cores的个数
比如说在提交job的时候没有指定executor-memory 和executor-cores的个数,那么就默认在一个Worker节点启动一个Executor,并且使用本台Worker的1G的内存和使用本台Worker的所有的cores
0、1、2、3、4、5代表数组中的索引
那么先遍历0号worker时,启动一个executor(启动前判断是否满足默认的条件(占用内存一个1G,核数是是否小于此worker核数))(判断依据是hashset和arraybuffer中存储的信息)minCoresPerExecuutor(即给这一个executor最小的核数 即一个核),然后遍历第二个worker,给他分配一个executor ,给他一个最小的核 。当所有的worker遍历完成后,然后开始第二轮遍历 即 给每一个executor再加一个core 以此类推 遍历的过程中 分配的核数是一直往上增加的,一直增加到将这个Worker节点所有的cores用完,但是使每个Worker节点的Executor的个数是一直置为1
最终是一个Worker只能启动一个Executor,并且这个Executor使用的是这个Worker上的所有的core
在这里插入图片描述
将spreadOutApps设置为true的意义是:轮询启动executor,使Spark集群分散启动Executor,即每个worker上都启动一个executor 每executor上分配较少的core 并不是只在一个worker节点启动一个executor然后启动最多的core然后再去启动下一个Executor 好处是:让不同executor分配到不同的worker上 为了更好的数据本地化(Spark分散启动Executor的默认机制
在这里插入图片描述
如果设置为False的话,比如一个application只需要10个核,那么第一台Worker就可以提供十个核 ,所有的计算都落到这一个节点上,还需要去其他更多的数据节点去拉取数据,更加的消耗了集群的资源。

指定–executor-cores的个数
以上是一个Worker中启动一个Executor(在代码中遍历的时候core的个数是一直往上加的,但是Executor的个数却一直是1,所以数组中的x号worker为当前的application都是启动了一个executor),那么什么时候一个worker不是启动一个executror而是启动很多个executor呢
即让coresPerExecutor的值不是空 则需要设置executor-cores的个数,设置之后,executor会轮询分布在各个Worker节点上,并且按照提交的核数进行相应的分配
coresPerExecutor不为空,那么oneExecutorPerWorker就是false。相应的过程是:遍历Worker节点的时候,如果此节点满足启动一个Executor的资源(即指定的核数与指定的内存的数量) ,那么先为0号Worker启动一个Executor,此时spreadOutApps为true,继续为其他的Worker分配Executor,第二轮遍历的时候,就会增加第一个Worker的Executor的个数
在这里插入图片描述
总结:
1、Executor在集群中分散启动,利于数据处理的本地化
2、如果提交任务时,不指定–executor-cores 集群则默认在每个Worker上启动一个Executor,这个Executor会使用这个Worker节点的所有的核和1G的内存
3、如果想要在一个Worker节点上启动多个Executor,则在提交任务的时候需要指定–executor-cores
4、启动Executor不仅和core有关还和内存有关
5、提交application要指定–total-executor-cores,否则当前application会使用集群中所有的core

猜你喜欢

转载自blog.csdn.net/weixin_38653290/article/details/86618777