spark on yarn作业运行的jar包缓存优化

本文详细阐述了在使用Spark on YARN时遇到的jar包缓存问题及其解决方法,包括HDFS上的jar包自动删除和nodemanager节点上的jar包自动清理。通过配置spark.yarn.jar和yarn-site.xml,成功解决了问题,提供了深入的学习和实践经验。
这几天一直在追查spark on yarn的作业运行中的jar包分发,以及执行后的jar包删除的问题。从一开始的毫无头绪,到后来逐渐清晰,到后来通过hadoop的两个很简单的参数配置解决了问题。不得不说,虽然问题不大,对某些大牛们来说也真是小case,但是追查问题,定位问题到最终解决问题的过程,对我来说真是很不错的体验和学习过程。下面详细描述一下遇到的问题,以及解决的过程,给后面的同学一点参考。

BTW,很多时候定位问题,查清楚问题的原因,比解决这个”问题“要更重要。


问题描述

spark版本是1.0.2,运行在cdh5.1.0-hadoop2.3.0版本的yarn上面。在每次提交作业执行之后,在我的HDFS的${yarn.app.mapreduce.am.staging-dir}/${username}/.sparkStaging下面,就会产生两个jar包,一个是spark-assembly-*.jar,另一个是提交执行的jar包。同时,在这个作业对应的executor的节点上,${yarn.nodemanager.local-dirs}目录下,也会有这两个jar包。而且不会被删除。

光spark-assembly*.jar就有一百多M,每次执行完都会留下这么多jar包,虽然不大,但可想而知,日积月累,可是个很麻烦的事情。所以,要搞清楚为啥会出现这个情况。


这个问题可以拆分成两个sub-task,一个是HDFS上的jar包为啥不会自动删除,一个是nodemanager节点上的jar包的自动删除。下面分别排查和解决:

HDFS上的jar包缓存

网上貌似很少有相关资料,就是找到了一个介绍如何配置spark.yarn.jar的文章,说是可以解决HDFS上的jar包缓存的问题。照着配置了一下,不起作用,又看作者说,要spark1.1.0以上的版本,所以重新编译了spark1.1.0,把集群的spark1.0.2升级到1.1.0。重新配置spark.yarn.jar,发现问题解决了。HDFS上仍然在每次提交作业时都会被上传两个jar包,但是用完后会自动删除。

配置方法

(1) 首先需要确保spark在1.1.0以上的版本。

(2) 在HDFS上建立一个公共lib库,比如/system/spark-lib/,设置权限为755。把spark-assembly-*.jar上传到公共lib库中。

(3) 在spark-env.sh中配置:

  1. <span style="font-size:14px;">spark.yarn.jar                          hdfs://yarncluster/system/spark_lib/spark-assembly-1.1.0-hadoop2.3.0-cdh5.1.0.jarspark.yarn.preserve.staging.files       false</span>  

**spark.yarn.jar配置成HDFS上的公共lib库中的jar包。这个配置项会使提交job时,不是从本地上传spark-assembly*.jar包,而是从HDFS的一个目录复制到另一个目录(不确定HDFS上的复制是怎么操作的),总的来说节省了一点时间。(网上有的文章里说,这里的配置,会节省掉上传jar包的步骤,其实是不对的,只是把从本地上传的步骤改成了在HDFS上的复制操作。)

**spark.yarn.preserve.staging.files: 这个配置项配置成false,表示在执行结束后,不保留staging files,也就是两个jar包。然后HDFS上的.sparkStaging下的两个jar包在作业执行完成后就会被删除。如果配置成true,执行完后HDFS上的.sparkStaging下两个jar包都会保存下来。

然后再运行,发现HDFS上.sparkStaging目录下不会再保留jar包。

问题定位

按道理来说,因为spark.yarn.preserve.staging.files默认是false,所以HDFS上的jar包是不会被保留的。但是在spark1.0.2中,却没有删除。我看了下1.0.2的代码,删除的机制是存在的:

//yarn/alpha/src/main/scala/org/apache/spark/deploy/yarn/ApplicationMaster.scala 

  1. <span style="font-size:14px;"><span style="font-family:Microsoft YaHei;font-size:12px;">  /**   * Clean up the staging directory.   */  private def cleanupStagingDir() {    var stagingDirPath: Path = null    try {      val preserveFiles = sparkConf.get("spark.yarn.preserve.staging.files""false").toBoolean      if (!preserveFiles) {        stagingDirPath = new Path(System.getenv("SPARK_YARN_STAGING_DIR"))        if (stagingDirPath == null) {          logError("Staging directory is null")          return        }        logInfo("Deleting staging directory " + stagingDirPath)        fs.delete(stagingDirPath, true)      }    } catch {      case ioe: IOException =>        logError("Failed to cleanup staging dir " + stagingDirPath, ioe)    }  }</span></span>  
按照这个逻辑,默认在AM关闭的时候,是会删除HDFS上的jar包的。不过没有正常删除。推测这应该是一个1.0.2里面的bug,而在1.1.0里面已经修复。


nodemanager节点上的jar包缓存

升级到1.1.0版本后,HDFS上的jar包问题就解决了。但是nodemanager节点上的jar包还是会保留。这个问题的定位很纠结,不过结果却出乎意料的简单。不说了,上结果吧。

配置方法

(1) 配置yarn-site.xml:
<span style="font-family:Microsoft YaHei;font-size:12px;"> <property>
<name>yarn.nodemanager.local-dirs</name>
<value>local-dir1, local-dir2,local-dir3</value>
</property>
<property>
<name>yarn.nodemanager.localizer.cache.target-size-mb</name>
<value>1024</value>
</property>
<property>
<name>yarn.nodemanager.localizer.cache.cleanup.interval-ms</name>
<value>1800000</value>
</property></span>
**yarn.nodemanager.local-dirs: 这个目录是nodemanager上的作业中间数据存放路径。推荐配置多个盘上的多个路径,从而分散作业执行中的磁盘IO压力。
**yarn.nodemanager.localizer.cache.target-size-mb:配置nodemanager上的缓存目录的最大限度。nodemanager上有一个deletion server服务,会定期检测,如果yarn.nodemanager.local-dirs中配置的目录大小(如果配置了多个,则计算多个目录的总大小)是否超过了这里设置的最大限度值。如果超过了,就删除一些已经执行完的container的缓存数据。
因为spark提交作业后遗留在nodemanager上的jar包就在yarn.nodemanager.local-dirs下面,所以只要这里配置合适的大小值。那么nodemanager上的deletion server是会自动检测并保证目录总大小的。所以只要配置了这个量,我们就不需要再担心nodemanager上的jar包缓存问题了,交给yarn就好了!很简单啊有木有,可就这么个问题,居然花了我一个星期的时间去定位。
**yarn.nodemanager.localizer.cache.cleanup.interval-ms: deletion server多长时间做一次检测,并且清除缓存目录直到目录大小低于target-size-mb的配置。
通过上面这三个量的配置,nodemanager会确保本地的缓存数据总量在target-size-mb之下,也就是超过了的话,之前的spark的jar包就会被删除。所以我们就不需要再担心nodemanager节点上的spark jar包缓存问题了。不过target-size-mb的默认值是10G,这个值当然可以根据你的实际情况进行调整。

问题定位

为什么mapreduce提交的任务在执行完后,会自动删除nodemanager上的所有中间文件,而spark却不会呢?

查看了下hadoop的源码(相关代码就不贴出来了),MapReduce提交作业的时候,中间数据是以application file对象的方式被处理的,也就是在nodemanager上是存放在username/appcache/目录下的,而按照yarn的处理机制,application file是专门存放中间数据的对象,所以MapReduce作业的所有中间数据在作业执行完后会被yarn删除。

而spark on yarn的任务提交后,两个jar包却是以yarn的分布式缓存(distributed cache)的方式存放和处理的。Distributed Cache是yarn提供给用户用来分发和管理作业执行中用到的extra file的处理机制。spark没有调用yarn的application file对象,却提交成distributed cache类型,不知道为什么。不过这就是jar包最终没有被自动删除的原因。

下面是MapReduce任务提交后nm上分发中间文件的日志:


可以看到中间文件都被下载到appcache的目录下。

再看看spark任务提交后nm上分发jar包的日志:


很明显jar包被存放在filecache目录下。也就是它是作为distributed cache的方式分发到nm上的。不知道为什么spark采用这样的设计方式。file cache在作业结束后,是不会主动被删除的。


就是这样了,这个问题其实根本也算不上一个问题。也就是一个深入学习spark on yarn的架构和解惑的过程。

更新:spark采用file cache而不是application cache的原因,是因为spark在每个NM上启动的是一个executor,每个task作为executor里的一个线程在运行。而只需要在executor启动时,在这个节点上用file cache机制分发一次jar包,然后在每个task执行时,能共享到通过file cache传过来的jar包。所以采用file cache机制的原因,是要在task之间共享jar包。

<think> 我们正在讨论SparkYARN上的使用、配置及运行机制。根据用户提供的引用内容,特别是引用[1]和引用[2]中关于YARN运行Spark和MapReduce的信息,我们可以进行如下组织: 1. **运行模式**:YARN-client和YARN-cluster模式的区别(引用[1]提到Driver的位置)。 2. **提交作业**:如何提交Spark作业YARN集群。 3. **配置**:关键配置项,特别是内存配置(括PermGen,虽然JDK1.8后移除了PermGen,但用户引用中提到了,所以需要说明)。 4. **运行机制**:结合引用[2]中MapReduce在YARN上的运行机制,类比SparkYARN上的运行机制(因为Spark的ApplicationMaster也是类似的工作方式)。 5. **常见问题**:比如内存配置问题、调试等。 注意:用户引用[3]是关于MapReduce的,但我们可以忽略,因为用户明确要求Spark。 下面开始组织回答: ### Spark on YARN 详解 #### 一、运行模式 Spark支持两种模式在YARN运行: 1. **YARN-client模式**(适用于交互式场景): - Driver运行在**提交作业的客户端机器**上 - Executor运行YARN容器中 - 日志可直接在客户端查看,但客户端必须保持在线 2. **YARN-cluster模式**(适用于生产环境): - Driver运行在**YARN集群的某个容器**中 - Executor同样运行YARN容器中 - 客户端提交作业后即可断开连接 > **注意**:在JDK1.8之前,YARN-cluster模式下Driver的JVM永久代(PermGen)可能使用默认值(如82MB),容易导致`OutOfMemoryError`。需通过`-XX:MaxPermSize`调整(但JDK1.8及以上已移除PermGen,改为Metaspace)[^1]。 #### 二、作业提交流程 ```mermaid flowchart TD Client[客户端] -->|1.提交应用| RM[ResourceManager] RM -->|2.分配容器| AM[Spark ApplicationMaster] AM -->|3.申请资源| RM RM -->|4.分配容器| NM[NodeManager] NM -->|5.启动Executor| Container[Executor容器] Container -->|6.注册与通信| AM ``` 1. **提交应用** 使用`spark-submit`提交作业: ```bash spark-submit --master yarn \ --deploy-mode cluster \ # 或client --class org.apache.spark.examples.SparkPi \ /path/to/examples.jar 100 ``` 2. **资源分配** - ResourceManager分配容器启动ApplicationMaster(AM) - AM向RM申请Executor资源(容器) - 每个Executor容器运行一个JVM进程 3. **任务执行** - AM将任务分发给Executor执行 - Executor通过心跳向AM汇报状态 #### 三、关键配置 在`spark-defaults.conf`中配置: | 配置项 | 说明 | 示例值 | |--------|------|--------| | `spark.yarn.am.memory` | AM内存 | 1g | | `spark.executor.memory` | Executor内存 | 2g | | `spark.executor.cores` | Executor核数 | 2 | | `spark.driver.memory` | Driver内存(client模式) | 1g | 环境变量配置(`spark-env.sh`): ```bash export HADOOP_CONF_DIR=/etc/hadoop/conf # 指向Hadoop配置目录 export YARN_CONF_DIR=/etc/hadoop/conf # 同上 ``` #### 四、内存优化指南 1. **堆内存管理** - Executor总内存 = `spark.executor.memory` + `spark.executor.memoryOverhead` - 内存溢出时增加`spark.yarn.executor.memoryOverhead`(默认executor内存的10%) 2. **Metaspace配置**(JDK1.8+) ```bash spark-submit --conf spark.executor.extraJavaOptions="-XX:MaxMetaspaceSize=256m" ... ``` 3. **堆外内存优化** 调整`spark.memory.offHeap.size`启用堆外内存 #### 五、问题排查 1. **查看日志** ```bash yarn logs -applicationId <app_id> ``` 2. **监控资源** - YARN Web UI:`http://<rm-address>:8088` - Spark Web UI:通过`http://<am-address>:4040`查看(需配置历史服务器) #### 六、与MapReduce对比 | **特性** | **MapReduce on YARN** | **Spark on YARN** | |----------|-----------------------|-------------------| | 计算模型 | 多阶段Map/Reduce | 内存迭代计算 | | 任务启动 | 每个任务单独JVM | Executor进程复用 | | 数据缓存 | HDFS读写 | 内存+磁盘缓存 | | 适用场景 | 批处理 | 批处理+流处理+机器学习 | --- ### 相关问题 1. 如何解决Spark on YARN的`NoSuchMethodError`类冲突问题? 2. 如何动态调整Spark on YARN的Executor数量? 3. Spark on YARN如何配置高可用(HA)? 4. 如何优化Spark Shuffle过程在YARN上的性能? 5. 在容器化环境中(如K8s)运行Spark on YARN有哪些注意事项?[^1][^2] </think> ### Spark on YARN 全面解析 SparkYARN上的运行结合了资源管理的高效性和Spark内存计算的灵活性,核心机制如下: #### 一、运行机制与架构 ```mermaid flowchart TD Client[客户端] -->|1.提交应用| RM[ResourceManager] RM -->|2.分配容器| AM[Spark ApplicationMaster] AM -->|3.申请资源| RM RM -->|4.分配容器| NM[NodeManager] NM -->|5.启动Executor| Container[Executor容器] Container -->|6.注册与通信| AM ``` 1. **两种部署模式** - **YARN-client模式** Driver运行在**客户端机器**上,直接控制任务执行(适合调试) ```bash spark-submit --master yarn --deploy-mode client ``` - **YARN-cluster模式** Driver运行在**YARN容器**中(生产环境推荐),需注意JVM配置差异[^1]: ```bash spark-submit --master yarn --deploy-mode cluster ``` 2. **核心组件交互** - ApplicationMaster(AM):Spark专属AM,负责向RM申请资源 - Executor:运行任务的容器,每个Executor对应一个JVM进程 - 资源本地化:优先将任务调度到数据所在的NodeManager #### 二、关键配置项 在`spark-defaults.conf`中配置: | **配置项** | **说明** | **示例值** | |------------|----------|------------| | `spark.yarn.am.memory` | AM内存 | 1g | | `spark.executor.memory` | Executor堆内存 | 4g | | `spark.executor.cores` | Executor核数 | 2 | | `spark.dynamicAllocation.enabled` | 动态资源分配 | true | **内存组成**: $$ \text{总容器内存} = \text{spark.executor.memory} + \text{spark.executor.memoryOverhead} $$ Executor实际内存含堆内存 + 堆外内存(默认取`max(384MB, 0.1 * executorMemory)`) #### 三、作业提交流程 1. **提交命令** ```bash spark-submit --master yarn \ --deploy-mode cluster \ --executor-memory 4g \ --num-executors 10 \ --class org.apache.spark.examples.SparkPi \ /path/to/examples.jar 100 ``` 2. **资源申请策略** - 初始申请:AM启动后立即申请`spark.dynamicAllocation.initialExecutors`个Executor - 动态伸缩:根据任务积压情况自动增减Executor(需启用`spark.dynamicAllocation.enabled`) #### 四、调优实践 1. **内存优化** - 解决PermGen问题(JDK1.8前): ```bash spark-submit --conf spark.executor.extraJavaOptions="-XX:MaxPermSize=256m" ``` - JDK1.8+优化Metaspace: ```bash spark-submit --conf spark.executor.extraJavaOptions="-XX:MaxMetaspaceSize=512m" ``` 2. **数据本地化** 配置等待本地化任务的超时时间: ```scala spark.locality.wait = 3s // 默认3秒 ``` 3. **Shuffle优化** ```scala spark.shuffle.service.enabled = true // 启用外部Shuffle服务 spark.shuffle.compress = true // 压缩Shuffle输出 ``` #### 五、故障排查 1. **日志查看** ```bash yarn logs -applicationId <app_id> # 获取完整日志 ``` 2. **Web UI监控** - YARN RM UI:`http://<rm-host>:8088` - Spark UI:通过`http://<am-host>:4040`访问(需提前配置端口转发) 3. **常见错误** - **Executor丢失**:检查内存配置,增加`spark.executor.memoryOverhead` - **类冲突**:使用`spark.jars.packages`指定依赖 #### 六、与MapReduce对比 | **特性** | **MapReduce on YARN** | **Spark on YARN** | |----------|------------------------|-------------------| | 计算模型 | 磁盘迭代 | 内存迭代 | | 任务启动 | 每个Task独立JVM | Executor复用JVM | | 延迟 | 高(分钟级) | 低(秒级) | | 适用场景 | 批处理 | 流处理+机器学习 | --- ### 相关问题 1. 如何解决Spark on YARN的`NoSuchMethodError`类冲突问题? 2. Spark动态资源分配在YARN上的工作原理是什么? 3. 如何配置Spark Executor的堆外内存防止OOM? 4. YARN队列资源足时如何优先保障Spark作业? 5. 在Kubernetes上部署SparkYARN部署有何优劣?[^1][^2
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值