一、引子

在Worker Actor中,每次LaunchExecutor会创建一个CoarseGrainedExecutorBackend进程,Executor和CoarseGrainedExecutorBackend是1对1的关系。也就是说集群里启动多少Executor实例就有多少CoarseGrainedExecutorBackend进程。

那么到底是如何分配Executor的呢?怎么控制调节Executor的个数呢?

二、Driver和Executor资源调度

下面主要介绍一下Spark Executor分配策略:

我们仅看,当Application提交注册到Master后,Master会返回RegisteredApplication,之后便会调用schedule()这个方法,来分配Driver的资源,和启动Executor的资源。

schedule()方法是来调度当前可用资源的调度方法,它管理还在排队等待的Apps资源的分配,这个方法是每次在集群资源发生变动的时候都会调用,根据当前集群最新的资源来进行Apps的资源分配。

Driver资源调度:

  随机的将Driver分配到空闲的Worker上去,详细流程请看我写的注释 :)
  1. // First schedule drivers, they take strict precedence over applications
  2. val shuffledWorkers = Random.shuffle(workers) // 把当前workers这个HashSet的顺序随机打乱
  3. for (worker <- shuffledWorkers if worker.state == WorkerState.ALIVE) { //遍历活着的workers
  4. for (driver <- waitingDrivers) { //在等待队列中的Driver们会进行资源分配
  5. if (worker.memoryFree >= driver.desc.mem && worker.coresFree >= driver.desc.cores) { //当前的worker内存和cpu均大于当前driver请求的mem和cpu,则启动
  6. launchDriver(worker, driver) //启动Driver 内部实现是发送启动Driver命令给指定Worker,Worker来启动Driver。
  7. waitingDrivers -= driver //把启动过的Driver从队列移除
  8. }
  9. }
  10. }

Executor资源调度:

 Spark默认提供了一种在各个节点进行round-robin的调度,用户可以自己设置这个flag
  1. val spreadOutApps = conf.getBoolean("spark.deploy.spreadOut", true)

在介绍之前我们先介绍一个概念,

可用的Worker:什么是可用,可用就是资源空闲足够且满足一定的规则来启动当前App的Executor。
Spark定义了一个canUse方法:这个方法接受一个ApplicationInfo的描述信息和当前Worker的描述信息。
1、当前worker的空闲内存 比 该app在每个slave要占用的内存 (executor.memory默认512M)大 
2、当前app从未在此worker启动过App
总结: 从这点看出,要满足:该Worker的当前可用最小内存要比配置的executor内存大,并且对于同一个App只能在一个Worker里启动一个Exeutor,如果要启动第二个Executor,那么请到其它Worker里。这样的才算是对App可用的Worker。
  1. /**
  2. * Can an app use the given worker? True if the worker has enough memory and we haven't already
  3. * launched an executor for the app on it (right now the standalone backend doesn't like having
  4. * two executors on the same worker).
  5. */
  6. def canUse(app: ApplicationInfo, worker: WorkerInfo): Boolean = {
  7. worker.memoryFree >= app.desc.memoryPerSlave && !worker.hasExecutor(app)
  8. }

SpreadOut分配策略:

SpreadOut分配策略是一种以round-robin方式遍历集群所有可用Worker,分配Worker资源,来启动创建Executor的策略,好处是尽可能的将cores分配到各个节点,最大化负载均衡和高并行。

下面看看,默认的spreadOutApps模式启动App的过程:

 
 1、等待分配资源的apps队列默认是FIFO的。
 2、app.coresLeft表示的是该app还有cpu资源没申请到:  app.coresLeft  = 当前app申请的maxcpus - granted的cpus
 3、遍历未分配完全的apps,继续给它们分配资源,
 4、usableWorkers =  从当前ALIVE的Workers中过滤找出上文描述的可用Worker,然后根据cpus的资源空闲,从大到小给Workers排序。
 5、当toAssign(即将要分配的的core数>0,就找到可以的Worker持续分配)
 6、当可用Worker的free cores 大于 目前该Worker已经分配的core时,再给它分配1个core,这样分配是很平均的方法。
 7、round-robin轮询可用的Worker循环
 8、toAssign=0时结束循环,开始根据分配策略去真正的启动Executor。
 
举例: 1个APP申请了6个core, 现在有2个Worker可用。
      那么: toAssign = 6,assigned = 2 
 那么就会在assigned(1)和assigned(0)中轮询平均分配cores,以+1 core的方式,最终每个Worker分到3个core,即每个Worker的启动一个Executor,每个Executor获得3个cores。
  1. // Right now this is a very simple FIFO scheduler. We keep trying to fit in the first app
  2. // in the queue, then the second app, etc.
  3. if (spreadOutApps) {
  4. // Try to spread out each app among all the nodes, until it has all its cores
  5. for (app <- waitingApps if app.coresLeft > 0) { //对还未被完全分配资源的apps处理
  6. val usableWorkers = workers.toArray.filter(_.state == WorkerState.ALIVE)
  7. .filter(canUse(app, _)).sortBy(_.coresFree).reverse //根据core Free对可用Worker进行降序排序。
  8. val numUsable = usableWorkers.length //可用worker的个数 eg:可用5个worker
  9. val assigned = new Array[Int](numUsable) //候选Worker,每个Worker一个下标,是一个数组,初始化默认都是0
  10. var toAssign = math.min(app.coresLeft, usableWorkers.map(_.coresFree).sum)//还要分配的cores = 集群中可用Worker的可用cores总和(10), 当前未分配core(5)中找最小的
  11. var pos = 0
  12. while (toAssign > 0) {
  13. if (usableWorkers(pos).coresFree - assigned(pos) > 0) { //以round robin方式在所有可用Worker里判断当前worker空闲cpu是否大于当前数组已经分配core值
  14. toAssign -= 1
  15. assigned(pos) += 1 //当前下标pos的Worker分配1个core +1
  16. }
  17. pos = (pos + 1) % numUsable //round-robin轮询寻找有资源的Worker
  18. }
  19. // Now that we've decided how many cores to give on each node, let's actually give them
  20. for (pos <- 0 until numUsable) {
  21. if (assigned(pos) > 0) { //如果assigned数组中的值>0,将启动一个executor在,指定下标的机器上。
  22. val exec = app.addExecutor(usableWorkers(pos), assigned(pos)) //更新app里的Executor信息
  23. launchExecutor(usableWorkers(pos), exec)  //通知可用Worker去启动Executor
  24. app.state = ApplicationState.RUNNING
  25. }
  26. }
  27. }
  28. } else {

非SpreadOut分配策略:

非SpreadOut策略,该策略:会尽可能的根据每个Worker的剩余资源来启动Executor,这样启动的Executor可能只在集群的一小部分机器的Worker上。这样做对node较少的集群还可以,集群规模大了,Executor的并行度和机器负载均衡就不能够保证了。
 
当用户设定了参数spark.deploy.spreadOut 为false时,触发此游戏分支,跑个题,有些困了。。
1、遍历可用Workers
2、且遍历Apps
3、比较当前Worker的可用core和app还需要分配的core,取最小值当做还需要分配的core
4、如果coreToUse大于0,则直接拿可用的core来启动Executor。。奉献当前Worker全部资源。(Ps:挨个榨干每个Worker的剩余资源。。。。)
 
举例: App申请12个core,3个Worker,Worker1剩余1个core, Worke2r剩7个core, Worker3剩余4个core.
这样会启动3个Executor,Executor1 占用1个core, Executor2占用7个core, Executor3占用4个core.
总结:这样是尽可能的满足App,让其尽快执行,而忽略了其并行效率和负载均衡。
  1. } else {
  2. // Pack each app into as few nodes as possible until we've assigned all its cores
  3. for (worker <- workers if worker.coresFree > 0 && worker.state == WorkerState.ALIVE) {
  4. for (app <- waitingApps if app.coresLeft > 0) {
  5. if (canUse(app, worker)) { //直接问当前worker是有空闲的core
  6. val coresToUse = math.min(worker.coresFree, app.coresLeft) //有则取,不管多少
  7. if (coresToUse > 0) { //有
  8. val exec = app.addExecutor(worker, coresToUse) //直接启动
  9. launchExecutor(worker, exec)
  10. app.state = ApplicationState.RUNNING
  11. }
  12. }
  13. }
  14. }
  15. }
  16. }

三、总结:

 1、 在Worker Actor中,每次LaunchExecutor会创建一个CoarseGrainedExecutorBackend进程,一个Executor对应一个CoarseGrainedExecutorBackend

2、针对同一个App,每个Worker里只能有一个针对该App的Executor存在,切记。如果想让整个App的Executor变多,设置SPARK_WORKER_INSTANCES,让Worker变多。

3、Executor的资源分配有2种策略:

3.1、SpreadOut :一种以round-robin方式遍历集群所有可用Worker,分配Worker资源,来启动创建Executor的策略,好处是尽可能的将cores分配到各个节点,最大化负载均衡和高并行。

3.2、非SpreadOut:会尽可能的根据每个Worker的剩余资源来启动Executor,这样启动的Executor可能只在集群的一小部分机器的Worker上。这样做对node较少的集群还可以,集群规模大了,Executor的并行度和机器负载均衡就不能够保证了。

行文仓促,如有不正之处,请指出,欢迎讨论 :)

补充:

1、关于:   一个App一个Worker为什么只有允许有针对该App的一个Executor 到底这样设计为何? 的讨论:

连城404:Spark是线程级并行模型,为什么需要一个worker为一个app启动多个executor呢?

朴动_zju:一个worker对应一个executorbackend是从mesos那一套迁移过来的,mesos下也是一个slave一个executorbackend。我理解这里是可以实现起多个,但起多个貌似没什么好处,而且增加了复杂度。

CrazyJvm@CodingCat 做了一个patch可以启动多个,但是还没有被merge。 从Yarn的角度考虑的话,一个Worker可以对应多个executorbackend,正如一个nodemanager对应多个container。 @OopsOutOfMemory

OopsOutOfMemory:回复@连城404: 如果一个executor太大且装的对象太多,会导致GC很慢,多几个Executor会减少full gc慢的问题。 see this post http://t.cn/RP1bVO4(今天 11:25)

连城404:回复@OopsOutOfMemory:哦,这个考虑是有道理的。一个workaround是单台机器部署多个worker,worker相对来说比较廉价。

JerryLead:回复@OopsOutOfMemory:看来都还在变化当中,standalone 和 YARN 还是有很多不同,我们暂不下结论 (今天 11:35)

JerryLead:问题开始变得复杂了,是提高线程并行度还是提高进程并行度?我想 Spark 还是优先选择前者,这样 task 好管理,而且 broadcast,cache 的效率高些。后者有一些道理,但参数配置会变得更复杂,各有利弊吧 (今天 11:40)

未完待续。。。

传送门:@JerrLead  https://github.com/JerryLead/SparkInternals/blob/master/markdown/1-Overview.md

——EOF——

原创文章,转载请注明出自:http://blog.csdn.net/oopsoom/article/details/38763985

Spark Executor Driver资源调度小结【转】的更多相关文章

  1. Spark Executor Driver资源调度汇总

    一.简介 于Worker Actor于,每次LaunchExecutor这将创建一个CoarseGrainedExecutorBackend流程.Executor和CoarseGrainedExecu ...

  2. Spark的Driver节点和Executor节点

    转载自:http://blog.sina.com.cn/s/blog_15fc03d810102wto0.html 1.驱动器节点(Driver) Spark的驱动器是执行开发程序中的 main方法的 ...

  3. Spark闭包 | driver & executor程序代码执行

    Spark中的闭包 闭包的作用可以理解为:函数可以访问函数外部定义的变量,但是函数内部对该变量进行的修改,在函数外是不可见的,即对函数外源变量不会产生影响. 其实,在学习Spark时,一个比较难理解的 ...

  4. [Spark内核] 第33课:Spark Executor内幕彻底解密:Executor工作原理图、ExecutorBackend注册源码解密、Executor实例化内幕、Executor具体工作内幕

    本課主題 Spark Executor 工作原理图 ExecutorBackend 注册源码鉴赏和 Executor 实例化内幕 Executor 具体是如何工作的 [引言部份:你希望读者看完这篇博客 ...

  5. 【Spark】榨干Spark性能-driver、exector内存突破256M

    榨干Spark性能-driver.exector内存突破256M spark driver memory 256m_百度搜索 Spark executor.memory - CSDN博客 sparkd ...

  6. Spark Executor内幕彻底解密:Executor工作原理图、ExecutorBackend注册源码解密、Executor实例化内幕、Executor具体工作内幕

    本课主题 Spark Executor 工作原理图 ExecutorBackend 注册源码鉴赏和 Executor 实例化内幕 Executor 具体是如何工作的 Spark Executor 工作 ...

  7. Spark Executor 概述

    Spark Executor 工作原理: 1. 在CoarseGrainedExecutorBackend启动时向Driver注册Executor,其实质是注册ExecutorBackend实例,和E ...

  8. Spark(五十):使用JvisualVM监控Spark Executor JVM

    引导 Windows环境下JvisulaVM一般存在于安装了JDK的目录${JAVA_HOME}/bin/JvisualVM.exe,它支持(本地和远程)jstatd和JMX两种方式连接远程JVM. ...

  9. 使用 JvisualVM 监控 spark executor

    使用 JvisualVM,需要先配置 java 的启动参数 jmx 正常情况下,如下配置 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmx ...

随机推荐

  1. HDU 4751 Divide Groups 2013 ACM/ICPC Asia Regional Nanjing Online

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4751 题目大意:判断一堆人能否分成两组,组内人都互相认识. 解题思路:如果两个人不是相互认识,该两人之 ...

  2. python操作RabbiMQ

    RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议. MQ全称为Message Queue, 消息队列(MQ)是一种应用程序 ...

  3. Jenkins 一: 环境安装以及配置

    安装JDK 下载地址: http://www.oracle.com/technetwork/java/javase/downloads/index.html 选择的JDK版本和开发使用的JDK版本最好 ...

  4. SVN强制注释

    1.目的 在使用SVN作为版本控制的时候,强制提交的人员写注释,这样能确保每次提交都有注释,方便查看 2.解决办法     2.1给工程加上属性          2.1.1在工程提交之后,通过客户端 ...

  5. caffe源代码分析--math_functions.cu代码研究

    当中用到一个宏定义CUDA_KERNEL_LOOP 在common.hpp中有. #defineCUDA_KERNEL_LOOP(i,n) \ for(inti = blockIdx.x * bloc ...

  6. phpcms如何嵌套循环

    PHPCMS V9的标签制作以灵活见长,可以自由DIY出个性的数据调用,对于制作有风格有创意的网站模板很好用,今天就介绍一个标签循环嵌套方法,可以实现对PC标签循环调用,代码如下: 在此文件里/php ...

  7. [转] __thread关键字

    http://blog.csdn.net/liuxuejiang158blog/article/details/14100897 __thread是GCC内置的线程局部存储设施,存取效率可以和全局变量 ...

  8. ActiveNotifications

    The NotificationManager can tell you how many notifications your application is currently showing. T ...

  9. 《CODE》讲了什么?

    本书首先从黑夜中用手电筒开关灯的方式来与小伙伴交流从而引出了编码与组合的概念,并阐明了编码的本质就是交流,是一种用来在机器与人之间传递信息的方式.然后在第 2~3 章中讲述了编码与组合的应用,如电报机 ...

  10. redis 自启动脚本

    看到网上许多手写的亦或复制的redis开机自启动脚本, 版本好多, 其实最简单的可以从下载的redis文件里找得到 我下载的redis是 3.0.3 版本的,  对于其他版本, 没有详细查看, 有需要 ...