Spark的资源调度

1、绪论

  在这里插入图片描述
  上图是Spark程序运行时的一个超级简单的概括。我们运行一个Spark应用程序时,首先第一步肯定是写一个Spark Application应用程序,然后调用资源调度器为Driver申请资源。申请成功后,向master为Application申请资源,申请完毕后,调用资源调度器把任务分发到节点执行。在各个节点进行分布式的并行计算。

2、前置知识

  对于Application来说,资源是Executor。对于Executor来说资源是内存、core。

  Master里面有几个对象:workers、waitingDrivers、waitingApps。下面对这几个对象做一个简单介绍,这几个对象是在源码中声明的,如需更详细的认知,可自行查看看源码(源码是使用的是Scala,如需快速了解,请参考《Scala快速学习》)。

val works = new HashSet[WorkInfo]()
val waitingDrivers = new ArrayBuffer[DriverInfo]()
val waitingApps = new ArrayBuffer[ApplicationInfo]()

  (可能直接看下面的知识点会有点迷惑,若不理解可以结合第三部分的流程图一起看)
  在上面的代码中,WorkInfo代表的是work节点的节点信息。DriverInfo是Driver发送过来的请求信息。ApplicationInfo是发送过来的Application的信息。

  val works = new HashSet[WorkInfo]()
  works 集合采用HashSet数组存储work的节点信息,可以避免存放重复的work节点。为什么要避免重复?首先我们要知道work节点有可能因为某些原因挂掉,挂掉之后下一次与master通信时会报告给master,这个节点挂掉了,然后master会在works对象里把这个节点去掉,等下次再用到这个节点是时候,再加进来。这样来说,理论上是不会有重复的work节点的。可是有一种特殊情况:work挂掉了,在下一次通信前又自己启动了,这时works里面就会有重复的work信息。

  val waitingDrivers = new ArrayBuffer[DriverInfo]()
  当客户端向master为Driver申请资源时,会将要申请的Driver的相关信息封装到master节点的DriverInfo这个泛型里,然后添加到waitingDrivers 里。master会监控这个waitingDrivers 对象,当waitingDrivers集合中的元素不为空时,说明有客户端向master申请资源了。此时应该先查看一下works集合,找到符合要求的worker节点,启动Driver。当Driver启动成功后,会把这个申请信息从waitingDrivers 对象中移除。

   val waitingApps = new ArrayBuffer[ApplicationInfo]()
  Driver启动成功后,会为application向master申请资源,这个申请信息封存到master节点的waitingApps 对象中。同样的,当waitingApps 集合不为空,说明有Driver向Master为当前的Application申请资源。此时查看workers集合,查找到合适的Worker节点启动Executor进程,默认的情况下每一个Worker只是为每一个Application启动一个Executor,这个Executor会使用1G内存和所有的core。启动Executor后把申请信息从waitingApps 对象中移除。

  注意点:上面说到master会监控这三个集合,那么到底是怎么监控的呢???
  master并不是分出来线程专门的对这三个集合进行监控,相对而言这样是比较浪费资源的。master实际上是‘监控’这三个集合的改变,当这三个集合中的某一个集合发生变化时(新增或者删除),那么就会调用schedule()方法。schedule方法中封装了上面提到的处理逻辑。

3、流程图

  在这里插入图片描述

4、详细步骤

  1、执行提交命令,会在client客户端启动一个spark-submit进程(用来为Driver申请资源)。
  2、为Driver向Master申请资源,在Master的waitingDrivers 集合中添加这个Driver要申请的信息。Master查看works集合,挑选出合适的Work节点。
  3、在选中的Work节点启动Driver进程(Driver进程已经启动了,spark-submit的使命已经完成了,关闭该进程)。
  4、Driver进程为要运行的Application申请资源(这个资源指的是Executor进程)。此时Master的waitingApps 中要添加这个Application申请的资源信息。这时要根据申请资源的要求去计算查看需要用到哪些Worker节点(每一个节点要用多少资源)。在这些节点启动Executor进程。
  (注:轮询启动Executor。Executor占用这个节点1G内存和这个Worker所能管理的所有的core)
  5、此时Driver就可以分发任务到各个Worker节点的Executor进程中运行了。

5、资源调度结论

  1、默认情况下,每一个Worker只会为每一个Application启动一个Executor。每个Executor默认使用1G内存和这个Worker所能管理的所有的core。
  2、如果想要在一个Worker上启动多个Executor,在提交Application的时候要指定Executor使用的core数量。提交命令:spark-submit --executor-cores
  3、默认情况下,Executor的启动方式是轮询启动,一定程度上有利于数据的本地化。

  什么是轮询启动???为什么要轮训启动呢???

  轮询启动:轮询启动就是一个个的启动。例如这里有5个人,每个人要发一个苹果+一个香蕉。轮询启动的分发思路就是:五个人先一人分一个苹果,分发完苹果再分发香蕉。

  为什么要使用轮询启动的方式呢???我们做大数据计算首先肯定想的是计算找数据。在数据存放的地方直接计算,而不是把数据搬过来再计算。我们有n台Worker节点,如果只是在数据存放的节点计算。只用了几台Worker去计算,大部分的worker都是闲置的。这种方案肯定不可行。所以我们就使用轮询方式启动Executor,先在每一台节点都允许一个任务。

  存放数据的节点由于不需要网络传输数据,所以肯定速度快,执行的task数量就会比较多。这样不会浪费集群资源,也可以在存放数据的节点进行计算,在一定程度上也有利于数据的本地化。

6、Spark的粗细粒度

  6.1、粗粒度(富二代)

    在任务执行之前,会先将资源申请完毕,当所有的task执行完毕,才会释放这部分资源。
    优点:每一个task执行前。不需要自己去申请资源了,节省启动时间。
    缺点:等到所有的task执行完才会释放资源,集群的资源就无法充分利用。

  6.2、细粒度(穷二代)

    Application提交的时候,每一个task自己去申请资源,task申请到资源才会执行,执行完这个task会立刻释放资源。
    优点:每一个task执行完毕之后会立刻释放资源,有利于充分利用资源。
    缺点:由于需要每一个task自己去申请资源,导致task启动时间过长,进而导致stage、job、application启动时间延长。

7、加深理解

  这里有几个小问题,益脑游戏。。。

  前提条件:假设我们有5个worker,每个worker节点提供10G内存,10个core。

  1、spark-submit --master … --executor-cores 2 --executor-memory 2G … 在集群中会启动多少个Executor进程???
  答:25
  分析:每个Executor进程使用2个core+2G内存。所以1台worker节点可以启动5个Executor。因为有5台worker节点。所以共可以启动5*5=25个Executor进程。

  2、spark-submit --master … --executor-cores 3 --executor-memory 4G … 在集群中会启动多少个Executor进程???
  答:10
  分析:根据提交命令,1个Executor进程需要占用3个core+4G内存。1个Worker节点有10个core和10G内存。若按照core来计算,10/3=3,可以启动3个Executor。若按照内存来算,,10/4=2,可以启动2个Executor。这样来看,内存不足,肯定启动不了3个Executor。所以,1个Worker启动2个Executor,5*2=10。

  3、spark-submit --master … --executor-cores 2 --executor-memory 2G --total-executor-cores 10 … 在集群中会启动多少个Executor进程???(–total-executor-cores:整个Application最多使用的core数)
  答:5
  分析:这个题比前两个多了一个限制条件,最多启动10个core。如果不考虑这个条件,这个题和第1题就一样了,应该是可以启动25个Executor,共用50个core。可是整个Application最多使用10个core,一个Executor使用2个core,所以10/2=5。只能启动5个Executor。

  4、spark-submit --master … --executor-cores 2 --executor-memory 2G --total-executor-cores 4 … 集群中Executor的分布情况???(–total-executor-cores:整个Application最多使用的core数)
  答:随机找两台Worker节点。
  分析:这个题和上一题的思路完全一样。因为core数限制,不能启动25个Executor,只能启动4/2=2个Executor进程。因为spark的任务启动方式是轮询启动,所以会随机找两台Worker节点启动Executor。

  5、启动Executor个数的公式:min(min(wm/em,wc/ec)*wn,tec/ec)
  注:
    --executor-cores : ec
    --executor-memory : em
    --total-executor-cores : tec
    worker_num : wn
    worker_memory : wm
    worker_core : wc
  分析:
    min(wm/em,wc/ec):根据要求的core数和内存容量,得到一台节点可以启动Executor的两个数值。这两个数取小值。
    x1 = min(wm/em,wc/ec)*wn:一台worker节点的Executor数*worker结点数=可以启动的总的Executor数。
    x2 = tec/ec:根据要求的总core数求出来可以启动的Executor总数。
    min(min(wm/em,wc/ec)*wn,tec/ec) == min(x1,x2):上面求出来的两个Executor数量取小值。

8、Spark更多内容

  如果想了解Spark更多内容,推荐阅读《Spark学习总结》。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值