在一个application内部,不同线程提交的Job默认按照FIFO顺序来执行,假设线程1先提交了一个job1,线程2后提交了一个job2,那么默认情况下,job2必须等待job1执行完毕后才能执行,如果job1是一个长作业,而job2是一个短作业,那么这对于提交job2的那个线程的用户来说很不友好:我这个job是一个短作业,怎么执行了这么长时间。

使用spark的公平调度算法可以在一定程度上解决这个问题,此时,job2不必等待job1完全运行完毕之后就可以获得集群资源来执行,最终的效果的就是,job2可能会在job1之前运行完毕。这对于一个更强调对资源的公平竞争的多用户场景下是非常有用的,每一个用户都可以获得等量的资源,当然你可以为每一个用户指定一个优先级/权重,优先级/权重越高,获得的资源越多,比如对于一个长作业,你可以为指定更高的权重,而对于短作业,指定一个相对较低的权重。

没有显示配置fairScheduler.xml下的公平调度算法

假设线程1提交了一个action,这个action触发了一个jobId为1的job。同时,在提交这个action之前,设置了spark.scheduler.pool:

SparkContext.setLocalProperty(“spark.scheduler.pool”,”pool_name_1”)

假设线程2提交了一个action,这个action触发了一个jobId为2的job。同时,在提交这个action之前,也设置了spark.scheduler.pool:

SparkContext.setLocalProperty(“spark.scheduler.pool”,”pool_name_1”)

假设线程3提交了一个action,这个action触发了一个JobId=3的job,但是这个线程并没有设置spark.scheduler.pool属性。

最后的spark 资源池逻辑上如下图所示:

rootPool这个池子里面有三个小池子,其名字分别为:pool_name1,pool_name2,default;pool_name1这个池子存储线程1提交的job,pool_name2存储线程2提交的job,default池子存储那些没有显示设置spark.scheduler.pool的线程提交的job,换句话说我们将不同线程提交的job给隔离到不同的池子里了。

每一个小池子都有以下三个可以配置的属性:weight,minshare,mode,他们的默认值如下:

weight=1

minshare=0

mode=FIFO

一个池子的weight值越大,其获得资源就越多,在上图中,因为这三个池子的weight值相同,所以他们将获得等量的资源。

一个池子的minShare表示这个池子至少获得的core个数。

mode可以是FIFO或者FAIR,如果为FIFO,那么池子里jobid越大的job(等价的,先提交的job),将越先获得集群资源;如果是FAIR,那么将采用一种更加公平的机制来调度job,这个后面再说。

显示配置fairScheduler.xml的公平调度算法

可以发现,上面那种通过在线程里设置spark.scheduler.pool的方式,所创建的池子的属性采用的都是默认值,而且一旦创建好之后你就不能再修改他们。spark提供了另外一种创建池子的方式,你可以配置conf/fairScheduler.xml文件,假设其内容如下(官方提供的内容):

 <?xml version="1.0"?>
<allocations>
<pool name="production">
<schedulingMode>FAIR</schedulingMode>
<weight>1</weight>
<minShare>2</minShare>
</pool>
<pool name="test">
<schedulingMode>FIFO</schedulingMode>
<weight>2</weight>
<minShare>3</minShare>
</pool>
</allocations>

如果配置了fairScheduler.xml文件,并且其内容如上所示,那么此时的spark 资源池的样子大致如下:

这个资源池里面同样有三个小池子,其名字分别为:production,test,default。其中production资源池的weight为2,他将获得更多的资源(与default池子相比),由于其minShare=3,所以他最低将获得3个core,其mode=FAIR,所以提交到这个池子里的job将按照FAIR算法来调度。

事实上,通过这两个图已经能够在脑海里对spark资源池产生一个大致的印象了,此时再去看spark 资源池的源码就会非常容易。

在初次阅读FIFO算法源码之前:需要重点关注两个属性,priority和stageId,其中的priority就是jobid。先提交的job,其jobid越小,因此priority就越小。finalStage其stageId最大,其parent stage 的stageId较小。

对于公平调度算法,给定两个池子a和b,谁优先获得资源?

1.如果a阻塞了但是b没有阻塞,那么先执行a

2.如果a没有阻塞,b阻塞了,先执行b

3.如果a和b都阻塞了,那么阻塞程度高(等待执行的task比例大) 的那个先执行

4.如果a和b都没有阻塞,那么资源少的那个先执行。

5.如果以上条件都不满足,那么按照a和b的名字来排序。

还是看一下这个算法的实现吧:

private[spark] class FairSchedulingAlgorithm extends SchedulingAlgorithm {
override def comparator(s1: Schedulable, s2: Schedulable): Boolean = {
val minShare1 = s1.minShare
val minShare2 = s2.minShare
val runningTasks1 = s1.runningTasks
val runningTasks2 = s2.runningTasks
val s1Needy = runningTasks1 < minShare1
val s2Needy = runningTasks2 < minShare2
val minShareRatio1 = runningTasks1.toDouble / math.max(minShare1, 1.0).toDouble
val minShareRatio2 = runningTasks2.toDouble / math.max(minShare2, 1.0).toDouble
val taskToWeightRatio1 = runningTasks1.toDouble / s1.weight.toDouble
val taskToWeightRatio2 = runningTasks2.toDouble / s2.weight.toDouble
var compare: Int = 0 //如果s1阻塞,s2没有阻塞,那么就先执行s1
if (s1Needy && !s2Needy) {
return true
} else if (!s1Needy && s2Needy) {
return false //如果s1没有,s2阻塞了,就先执行s2
} else if (s1Needy && s2Needy) {//如果二者都阻塞了,那就看谁阻塞程度大
compare = minShareRatio1.compareTo(minShareRatio2)
} else {//都没阻塞,那么看谁的资源少。
compare = taskToWeightRatio1.compareTo(taskToWeightRatio2)
} if (compare < 0) {
true
} else if (compare > 0) {
false
} else {
s1.name < s2.name//实在不行了,按照池子的name排序吧。
}
}
}

源码走读

TaskSchedulerImpl在收到DAGScheduler提交的TaskSet时执行如下方法:

  override def submitTasks(taskSet: TaskSet) {
val tasks = taskSet.tasks
logInfo("Adding task set " + taskSet.id + " with " + tasks.length + " tasks")
this.synchronized {
//创建TaskSetManager
val manager = createTaskSetManager(taskSet, maxTaskFailures)
val stage = taskSet.stageId
val stageTaskSets =
taskSetsByStageIdAndAttempt.getOrElseUpdate(stage, new HashMap[Int, TaskSetManager])
stageTaskSets(taskSet.stageAttemptId) = manager
val conflictingTaskSet = stageTaskSets.exists { case (_, ts) =>
ts.taskSet != taskSet && !ts.isZombie
}
if (conflictingTaskSet) {
throw new IllegalStateException(s"more than one active taskSet for stage $stage:" +
s" ${stageTaskSets.toSeq.map{_._2.taskSet.id}.mkString(",")}")
} //将TaskSetManager添加到资源池,properties里面存储了我们调用SparkContext.setLocalProperty时传递的poolName
schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties) if (!isLocal && !hasReceivedTask) {
starvationTimer.scheduleAtFixedRate(new TimerTask() {
override def run() {
if (!hasLaunchedTask) {
logWarning("Initial job has not accepted any resources; " +
"check your cluster UI to ensure that workers are registered " +
"and have sufficient resources")
} else {
this.cancel()
}
}
}, STARVATION_TIMEOUT_MS, STARVATION_TIMEOUT_MS)
}
hasReceivedTask = true
}
//调用CoarseGrainedSchedulerBakckend的reviveOffers()
backend.reviveOffers()
}

这个方法主要做两件事情:第一创建TaskSetManager然后将其添加到资源池,第二调用CoarseGrainedSchedulerBackend进行资源调度。

我们重点讲解资源池的构造和资源池的添加,因此重点关注schedulerBuilder。他是一个trait,主要有两个实现:FIFOSchedulableBuilder和FairSchedulableBuilder。

/**
* An interface to build Schedulable tree
* buildPools: build the tree nodes(pools)
* addTaskSetManager: build the leaf nodes(TaskSetManagers)
*/
private[spark] trait SchedulableBuilder {
def rootPool: Pool //构建资源池,在创建SchedulerBuilder时,会调用buildPools方法来构建池子
def buildPools() //将资源池添加到rootPool中
def addTaskSetManager(manager: Schedulable, properties: Properties)
}

这里重点关注他的FairSchedulerBuilder。

addTaskSetManager

 override def addTaskSetManager(manager: Schedulable, properties: Properties) {
var poolName = DEFAULT_POOL_NAME
//
var parentPool = rootPool.getSchedulableByName(poolName) //如果用户设置了spark.scheduler.pool
if (properties != null) {
//默认用户设置的spark.scheduler.pool的值
poolName = properties.getProperty(FAIR_SCHEDULER_PROPERTIES, DEFAULT_POOL_NAME)
parentPool = rootPool.getSchedulableByName(poolName) //如果没有这个池子,就创建一个新的池子
if (parentPool == null) {
// we will create a new pool that user has configured in app
// instead of being defined in xml file
//此时的mode,minshare,weight都采用默认值,因此可以发现,在通过设置spark.scheduler.pool这种方式生成的池子
//采用的都是默认值
parentPool = new Pool(poolName, DEFAULT_SCHEDULING_MODE,
DEFAULT_MINIMUM_SHARE, DEFAULT_WEIGHT)
//最后将新创建的池子添加到rootPool中。
rootPool.addSchedulable(parentPool)
logInfo("Created pool %s, schedulingMode: %s, minShare: %d, weight: %d".format(
poolName, DEFAULT_SCHEDULING_MODE, DEFAULT_MINIMUM_SHARE, DEFAULT_WEIGHT))
}
}
parentPool.addSchedulable(manager)
logInfo("Added task set " + manager.name + " tasks to pool " + poolName)
}

从这里可以看出,通过在线程里使用SparkContext.setLocalProperty来设置spark.scheduler.pool所生成的资源池,其weight,minShare,mode采用的都是默认值,这在某些场景可能不满足用户要求,此时就需要显示的配置fairScheduler.xml文件了。

如果用户创建了fairScheduler.xml,那么会调用buildPools读取这个文件,来创建用户配置的池子:

override def buildPools() {
var is: Option[InputStream] = None
try {
is = Option {
schedulerAllocFile.map { f =>
new FileInputStream(f)
}.getOrElse {
Utils.getSparkClassLoader.getResourceAsStream(DEFAULT_SCHEDULER_FILE)
}
} //构建fairScheduler.xml中指定的池子
is.foreach { i => buildFairSchedulerPool(i) }
} finally {
is.foreach(_.close())
} // finally create "default" pool
//构建默认池子,也就是default池子。
buildDefaultPool()
}
 private def buildFairSchedulerPool(is: InputStream) {
val xml = XML.load(is)
for (poolNode <- (xml \\ POOLS_PROPERTY)) { val poolName = (poolNode \ POOL_NAME_PROPERTY).text
var schedulingMode = DEFAULT_SCHEDULING_MODE
var minShare = DEFAULT_MINIMUM_SHARE
var weight = DEFAULT_WEIGHT val xmlSchedulingMode = (poolNode \ SCHEDULING_MODE_PROPERTY).text
if (xmlSchedulingMode != "") {
try {
schedulingMode = SchedulingMode.withName(xmlSchedulingMode)
} catch {
case e: NoSuchElementException =>
logWarning("Error xml schedulingMode, using default schedulingMode")
}
} val xmlMinShare = (poolNode \ MINIMUM_SHARES_PROPERTY).text
if (xmlMinShare != "") {
minShare = xmlMinShare.toInt
} val xmlWeight = (poolNode \ WEIGHT_PROPERTY).text
if (xmlWeight != "") {
weight = xmlWeight.toInt
} //创建用户配置的池子
val pool = new Pool(poolName, schedulingMode, minShare, weight)
rootPool.addSchedulable(pool)
logInfo("Created pool %s, schedulingMode: %s, minShare: %d, weight: %d".format(
poolName, schedulingMode, minShare, weight))
}
}

总结:

如果开启了fari公平调度算法,并且在提交action的线程里面设置了sparkContext.setLocalPropery("spark.scheduler.pool",poolname),那么这个线程提交的所有job都被提交到poolName指定的资源池里,如果poolName指定的资源池不存在,那么将使用默认值来自动创建他。一种更加灵活的创建池子的方式是用户显示的配置fairScheduler.xml文件,你可以显示的设置池子的weight,minShare,mode值。

由于本人接触spark时间不长,如有错误或者任何意见可以在留言或者发送邮件到franciswbs@163.com,让我们一起交流。

作者:FrancisWang

邮箱:franciswbs@163.com
出处:http://www.cnblogs.com/francisYoung/
本文地址:http://www.cnblogs.com/francisYoung/p/5209798.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

Spark 资源池简介的更多相关文章

  1. Spark Streaming简介及原理

    简介: SparkStreaming是一套框架. SparkStreaming是Spark核心API的一个扩展,可以实现高吞吐量的,具备容错机制的实时流数据处理. 支持多种数据源获取数据: Spark ...

  2. spark生态圈简介

    原文引自:http://www.cnblogs.com/shishanyuan/p/4700615.html 1.简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algori ...

  3. [翻译]Apache Spark入门简介

    原文地址:http://blog.jobbole.com/?p=89446 我是在2013年底第一次听说Spark,当时我对Scala很感兴趣,而Spark就是使用Scala编写的.一段时间之后,我做 ...

  4. Spark RDD简介与运行机制概述

    RDD工作原理: 主要分为三部分:创建RDD对象,DAG调度器创建执行计划,Task调度器分配任务并调度Worker开始运行. SparkContext(RDD相关操作)→通过(提交作业)→(遍历RD ...

  5. Spark SQL概念学习系列之SQL on Spark的简介(三)

    AMPLab 将大数据分析负载分为三大类型:批量数据处理.交互式查询.实时流处理.而其中很重要的一环便是交互式查询. 大数据分析栈中需要满足用户 ad-hoc.reporting. iterative ...

  6. 【Spark学习】Apache Spark项目简介

    引言:本文直接翻译自Spark官方网站首页 Lightning-fast cluster computing 从Spark官方网站给出的标题可以看出:Spark——像闪电一样快的集群计算 Apache ...

  7. 001 Spark的简介以及入门

    1.hadoop,spark,Flink的比较 MapReduce: 分布式的计算框架 -> Hive 问题: shuffle:大文件的排序+读写磁盘+网络传输 => 比较慢 只有两种执行 ...

  8. Spark standalone简介与运行wordcount(master、slave1和slave2)

    前期博客 Spark standalone模式的安装(spark-1.6.1-bin-hadoop2.6.tgz)(master.slave1和slave2)  Spark运行模式概述 1. Stan ...

  9. Spark Streaming简介

    离线计算和实时计算对比 1)数据来源 离线:HDFS历史数据 数据量比较大 实时:消息队列(Kafka),实时新增/修改记录过来的某一笔数据 2)处理过程 离线:MapReduce: map+redu ...

随机推荐

  1. libvirt 安装篇

    1. 环境:Centos 7     python 2.7 2. 安装 sudo yum install  gcc  python-devel  libvirt libvirt-devel sudo ...

  2. java后台对前端输入的特殊字符进行转义

    转自:http://www.cnblogs.com/yangzhilong/p/5667165.html java后台对前端输入的特殊字符进行转义 HTML: 常见的帮助类有2个:一个是spring的 ...

  3. Eclipse最常用快捷键

    常用快捷键: Ctrl + 1 :快速修复(当编辑器出现红色波浪线时使用此快捷键能快速弹出提示) Ctrl + d :删除当前光标所在的行 Ctrl + z :撤销上一步的操作 Ctrl + y :重 ...

  4. 使用ngrok将内网映射为外网

    如何将自己的本地服务器映射到外网上去?我们可以使用ngrok这个工具,下载地址:http://pan.baidu.com/s/1slnMwPn 具体的操作步骤如下: 第一步.下载客户端我们建议下载的时 ...

  5. Ubuntu 安装php+mysql 环境

    新系统安装完毕后,首先运行apt-get update 更新apt库. 然后安装ssh,输入apt-get install openssh-server,安装ssh是为了可以远程操作,不然坐在机房实在 ...

  6. [Sass]局部变量和全局变量

    [Sass]局部变量和全局变量 Sass 中变量的作用域在过去几年已经发生了一些改变.直到最近,规则集和其他范围内声明变量的作用域才默认为本地.如果已经存在同名的全局变量,从 3.4 版本开始,Sas ...

  7. Xcode静态检查分析代码

    Clang静态分析和Instruments来剖析代码有一些不同,Clang更致力于在编译的过程中通过自身的一套判断机制来找出代码中潜在的隐患.   在XCode 3.2之后的版本里,Clang已经被集 ...

  8. python笔记7:日期和时间

    Python 提供了一个 time 和 calendar 模块可以用于格式化日期和时间. 时间间隔是以秒为单位的浮点小数. 每个时间戳都以自从1970年1月1日午夜(历元)经过了多长时间来表示. 时间 ...

  9. 用代码控制鼠标键盘(C#语言)

    前些时间想做一个鼠标点击器,用到了这些知识. 下面整理记录一下. ps.感谢各位大神 下面直接上代码 1.鼠标的控制 class MouseMove { #region MouseEvent [Sys ...

  10. jQuery简单实现iframe的高度根据页面内容自适应的方法(转)

    本文实例讲述了jQuery简单实现iframe的高度根据页面内容自适应的方法.分享给大家供大家参考,具体如下: 方式1: //注意:下面的代码是放在和iframe同一个页面中调用 $("#i ...