云上大数据系列2:如何最大化利用你的集群资源

  1. 云栖社区>
  2. 博客>
  3. 正文

云上大数据系列2:如何最大化利用你的集群资源

子淑 2018-07-21 10:31:49 浏览1018

本篇是云上大数据系列第二篇文章,主要介绍Hadoop系统的基础调优,让Hadoop集群的资源能够被充分利用起来。在后续的文章中,我们还将会分享更多关于云上大数据系统的性能分析和调优经验,敬请期待。

大数据系统对资源的占用较大,如果不进行合适的基础调优,很容易造成资源的浪费。尤其是在云端部署大数据系统,按量计费却没有最大化利用购买的资源,往往导致投入产出比较低。本篇我们介绍如何对Hadoop系统进行基础优化,让 Hadoop 系统的资源能够被充分利用起来。

  • 资源环境:ecs.d1.6xlarge × 5
  • 软件系统:CDH 5.14.2 (Spark 1.6)
  • 操作系统:CentOS 7.3

我们以 CDH 5.14.2 为例,介绍 Spark-on-YARN 的基础调优方法,在这一版本的 CDH 中,Spark 版本是 1.6 。值得注意的是,Spark 1.6 以后(含),其内存管理方式发生了变化,本文论述的方法不一定适用于之前的版本。阅读本文前,你需要有一定的 Hadoop 使用或开发经验。

1. Spark-on-YARN 的资源分配

提交 Spark 任务的时候,YARN 在做什么?

YARN 将两类主要资源(CPUmemory)打包为 containerSpark 作为运行在 YARN 上的应用程序,每次使用 spark-submit(或其他方式)提交新的任务,都会先向YARN 申请资源。简要过程可以归为:

  • ResourceManager选择一个结点启动一个准备运行 ApplicationMastercontainer
  • 如果是 cluster 模式部署,则进一步启动 SparkContext
  • 接着,ApplicationMasterResourceManager 申请运行该任务所需要的资源;
  • 在得到资源以后通知相应的 NodeManager 启动运行 Spark Executorcontainer
  • Spark DriverSpark Executor 分配 taskExecutortask 的执行情况汇报给 Driver

下文中的两幅示意图简要地展示了整个启动的流程。

如何合理地为 Spark 配置集群资源

Spark 资源的分配牵扯到 SparkYARN 上的两种不同的部署模式:

  • client 模式:Spark Driver 运行在提交任务的客户端。
  • cluster 模式:Spark Driver 运行在 ApplicationMaster 中;

下面就每一种模式下如何配置资源,做详细介绍:

1) client 模式:

spark_on_yarn_client

client 模式下,Spark Drivr 运行在提交任务的客户端,不需要单独为其配置资源,只需要为 ApplicationMasterSpark Executor 分配合适的资源即可。

ApplicationMaster 分配资源

client 模式下,ApplicationMaster 的作用仅限于资源的申请和分配,可以为其分配少量的资源即可,例如按照默认值,分配 1vcore512M 内存:

spark.yarn.am.cores=1
spark.yarn.am.memory=512m

如果我们仅配置这两项,YARN实际申请的资源有可能比这个配置大的多,原因在于 spark.yarn.am.memory 仅限制了ApplicationMaster(JVM进程)的堆内存空间,对于非堆内存空间,也需要做配置:

spark.yarn.am.memoryOverhead=128m

默认情况下,该值是通过下面这个公式计算得来:

spark.yarn.am.memoryOverhead=max(spark.yarn.am.memory*0.1, 384)m

此时,我们通过 ResourceManager 界面观察 YARN 的资源分配情况,有可能发现实际分配的资源比 896m (512m+384m) 还要大。这是由于 YARN 对于资源的分配存在最小粒度,总是按照最小粒度的整数倍来分配资源。

如果你用的是 Capacity Scheduler(默认调度器),这个粒度通过下面变量来控制:

yarn.scheduler.minimum-allocation-mb

如果你用的是 Fair Scheduler,则通过下面变量来控制:

yarn.scheduler.increment-allocation-mb

这两个变量控制了 YARN 为每个 container 分配内存空间的最小尺寸。当用户申请的内存空间小于该尺寸的时候,YARN 会按照这个最小尺寸来分配;当用户申请的内存空间大于该尺寸的时候,YARN 会按照该尺寸的整数倍来分配空间。默认值都是 1024m

例如,上例中,YARN 实际为 ApplicationMaster 分配的空间应该是 1024m

切换这两种调度器,你可以:

yarn.resourcemanager.scheduler.class=org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler

当然,YARNcontainer 分配内存也存在上线,可以通过

yarn.scheduler.maximum-allocation-mb

来控制。用户申请的内存空间超过该值,YARN 会拒绝分配并报出相应的错误。

对于 CPU 的分配,也存在同样的策略,因为比较简单,这里就不再赘述,读者对照上述关于内存的介绍,看下配置文件中相应的配置的名称便能理解。

为了便于理解,笔者画了一张示意图总结一下上述的分配策略:
yarn_am_mem_allocation_strategy_client

Spark Executor 分配资源

Spark Executor 被分配到 Node Manager 结点。在每个结点上,需要为操作系统和 Hadoop 的其他进程分配一点资源。因此,总体可分配的资源比结点固有资源要少一些。Executor 的资源分配从以下几个方面来入手:

每个 Executor 分配多少 CPU 资源?

我们先来讨论这个问题,因为这决定了最终需要分配多少 Executor

ecs.d1.6xlarge的每个结点有 24 Core96G内存。理论上每个 Executor 最多可以分配到 24Core。但如上所述,我们需要为操作系统和其他进程预留一部分资源,因此实际可分配的资源少于 24Core96G 内存。

Core 的数量决定了任务运行的并发度,是不是并发度越高越好呢?实验表明,HDFS 的一次读/写并发超过 5 以后,性能就会急剧下降。因此,这里推荐将每个 ExecutorCore 数设为 5

spark.executor.cores=5

此时,每个结点可以起 4Executor。我们预留了 4Core 给其他进程。

每个结点分配多少个 Executor

每个结点起 4Executor4 个几点总共可以起 16Executor

spark.executor.instances=16

值得注意的是, ApplicationMaster 可能被调度到任意结点,我们预留的 4Core 已经足够。

每个 Executor 分配多少内存资源?

每个结点总共 96G 内存空间,我们为其他进程预留 4G 内存,剩余 92G 内存可以为 4Executor 平均分配 23G 内存。考虑到 Executor 也存在非堆内存:

spark.yarn.executor.memoryOverhead=max(spark.executor.memory*0.1, 384)m

23 * 1024 * 0.1 > 384

因此,23G 内存中需要预留 10% 的空间给非堆内存,堆内存实际分配到的空间为:

spark.executor.memory=23*1024*0.9=21196m

此时,每个 Executor 申请的内存空间为:

21196+21196*0.1=23315.6m ~ 22.7G

我们知道 YARN 对于内存资源的分配存在最小粒度,如果此时最小粒度是 1024m,那么实际 YARN 为每个 Executor Container 分配到的内存空间是 23G

值得注意的是,如果你为 Application Master 分配的内存过大,超过了预留的 4G,那么上述的资源分配策略将会失效, YARN 会因为无法按照上述策略分配资源而报错。

另外需要注意的是,Executor 内存不宜过大,否则 Java 虚拟机对于内存的管理将存在很大的负担,往往容易造成 GC 非常严重的后果。

如果你按照上述配置来启动集群,并且成功提交了一个 Spark 任务,你大概率会发现一个问题:一个 Executor 也没起来。是哪里出问题了呢?上面我们一直在论述 Spark 资源的申请分式,却忽略了资源是由 YARN 来分配的事实。YARN 对于每个 NodeManager 的资源都设定了一个上线例如:每个 NodeManager 可以分配的最大内存是(默认 8G ):

yarn.nodemanager.resource.memory-mb

我们申请的 23G 内存远远超过了 YARN 的允许,因此无法为 Executor 分配 Container
同样的,对于 CPUYARN 也有规定:

yarn.nodemanager.resource.cpu-vcores

在实际的配置中,我们要格外注意这一点。

2) cluster 模式

spark_on_yarn_cluster

cluster 模式下,Spark Driver 运行在 ApplicationMaster 进程中,该进程又被调度在集群的某个结点上,因此,需要通过 Spark 的相关配置来决定 ApplicationMaster 实际需要申请多少资源。ApplicationMaster 占用了集群中某个结点的资源,那么该结点上可以分配给 Spark Executor 的资源就相应的减少了,可分配的 Executor 数量也会相应的减少。

ApplicationMaster 分配资源

默认情况下,Spark会为Driver申请如下资源(spark-defaults.conf):

spark.driver.cores=1
spark.driver.memory=512m

现在读者已经明白了 YARN 的资源分配策略,知道实际分配的资源可能远大于上述的配置,使用

spark.yarn.driver.memoryOverhead=max(spark.driver.memory*0.1, 384)m

控制非堆内存;分配策略使用下面这张示意图可以比较清楚的表示出来:
yarn_am_mem_allocation_strategy_cluster

在实际的配置中,需要注意:由于 Spark Driver 运行在 Application Master 进程中,需要适当地调高 Application Master 的内存大小。

Spark Executor 分配资源

Spark Executorcluster模式下的分配策略和在 client模式下相同,这里不再赘述。需要注意的一点是:调高后的 Application Master 内存有可能超过预留的内存大小,此时,如果有必要,适当调低每个 Executor 的内存大小。

2. Spark 1.6 的内存模型

1.6 以后,Spark 采用一种叫统一内存管理模型的方式管理内存空间,如果你想要切换回静态内存分配方式,可以:

spark.memory.useLegacyMode=true

下图比较清晰得展示了统一内存管理模型下内存空间的划分:
spark_unified_mem_model

Reserved Memory 是系统预留的空间,默认是 300M,存储了 Spark 的一些内部对象,不能用来做数据缓存或存储计算的中间结果。在生产环境中,是不能改变的。在测试环境下可以通过下面的参数修改:

spark.testing.reservedMemory

如果为 Executor 申请的堆内存空间小于预留空间的 1.5 倍,Spark 会报错,提示你申请更大的内存空间。

User Memory 是完全由用户掌控的内存空间,默认大小为:

User_Memory_Size=(JVM_Heap_Size - Reserved_Memory_Size)*(1.0 - spark.memory.fraction)

需要注意的是,如果这块内存使用不当,是可以造成内存溢出的。Spark 不会保证这块内存的安全。

Spark Memory 顾名思义是 Spark 管理的内存空间,分为存储和计算两部分,可以通过

spark.memory.storageFraction

来改变两部分的比例。 Storage Memory 主要用来缓存从 HDFS 读取的数据或者计算的中间结果。 Execution Memory 则存储执行 task 过程中的一些对象或者 shuffle 过程中的临时数据(例如准备排序的数据)。统一内存模型以后,这两部分的内存空间可以相互借用。具体的借用方式有:

  • 一方空闲,一方内存不足情况下,内存不足一方可以向空闲一方借用内存;
  • Execution Memory 可以强制拿回 Storage MemoryExecution Memory 空闲时,借用的 Execution Memory 的部分内存(强制取回,而 Storage Memory 数据丢失,重新计算即可);
  • Storage Memory 只能等待 Execution Memory 主动释放占用的 StorageMemory 空闲时的内存(不强制取回,因为如果 task 执行,数据丢失就会导致 task 失败)。

这就意味着,当我们在配置 Storage Memory 的时候,最好不要小于其初始值大小。因为其内存空间总是会被 Execution Memory 占用而不能强制释放,这就造成如果 Storage Memory 很小,那么其实际可用的空间将更小,缓存数据的功能将被大大减弱。

希望这篇文章能对进行 Spark-on-YARN 性能调优的同学有所帮助。