ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spark 3.2.0 on YARN 安装配置与Executor资源优化实践

Spark 3.2.0 on YARN 安装配置与Executor资源优化实践 简介Spark 3.2.0是Apache Spark的重要版本这份针对Hadoop 3.2构建的tgz安装包适合大数据开发、数据分析与机器学习场景面向需要搭建Spark集群或本地开发环境的工程师与学习者。包内含完整二进制发行内容覆盖Spark Core、SQL、Streaming、MLlib和GraphX核心模块并提供Python、Scala、Java等多语言API及执行脚本。压缩包共1476个文件以py、jar、scala、java、txt等类型为主包含可执行命令、配置模板与测试样例整体约287MB。已有1121人学习下载。解压后可直接在Hadoop 3.2环境中运行便于学习Kubernetes集成、时间旅行、Catalyst优化器等新特性也可作为研究源码、编写DataFrame作业或调试集群配置的完整参考。1. 为什么我盯上了这个安装包版本命名与选型细节1.1 从文件名读出所有信息先说这个文件名本身spark-3.2.0-bin-hadoop3.2.tgz。很多人从Apache官网下载Spark的时候看到一堆类似的名字就犯晕——spark-3.2.0-bin-hadoop2.7、spark-3.2.0-bin-hadoop3.2、spark-3.2.0-without-hadoop到底该下哪个其实这个命名规则非常直白spark-3.2.0是Spark版本号bin表示这是预编译好的二进制发行版开箱即用不需要自己构建源码hadoop3.2说明这个包是针对 Hadoop 3.2 系列编译的tgz就是tar.gz压缩格式。换句话说这个包在编译时构建了对Hadoop 3.2.x的依赖支持下载后直接用就行。我当时选择Spark 3.2.0主要是因为它是一个非常成熟稳定的版本支持Java 8和Java 11既有新的Kubernetes特性也有稳定的YARN集成社区资料丰富。最关键的是我的Hadoop集群正好是3.2.x选这个包天然匹配不用额外折腾兼容性问题。1.2 预编译包和源码编译怎么选这里我多啰嗦一句。bin后缀的包自带了对特定Hadoop版本的HDFS、YARN客户端依赖安装者不需要自己处理复杂的依赖。而without-hadoop版本则适合那些想完全自定义Hadoop依赖的场景比如你的Hadoop版本很冷门、或者需要在同集群里混编多个版本组件时。对绝大多数场景我的建议是先确认你集群Hadoop的主版本然后直接选对应后缀的bin包。比如Hadoop 3.2.x就选hadoop3.2Hadoop 2.7.x就选hadoop2.7。不要轻易碰源码编译除非你有明确的定制需求否则只是徒增工作量。还有一点值得注意如果你后续要在YARN上跑任务那么SPARK_DIST_CLASSPATH和HADOOP_CONF_DIR这两个环境变量非常关键前者让Spark能找到Hadoop的各个依赖jar包后者让Spark能读到HDFS和YARN的配置文件。这两个地方配错了最常见的报错是各种 ClassNotFoundException 或者连接不上NameNode后面我会具体讲。2. 装之前必须确认的三件事JDK、Hadoop与系统级准备2.1 JDK版本与Spark 3.2.0的兼容性Spark 3.2.0官方支持Java 8和Java 11这一点在装之前就要想清楚。我见过不少同学明明装的是JDK 17结果Spark一启动就报UnsupportedClassVersionError然后浪费半天查问题。如果你的服务器上还没有JDK我建议直接装JDK 8比如1.8.0_202以上的版本理由很简单绝大多数开源大数据组件对JDK 8的兼容性最好排查问题也能搜到最多现成答案。如果非要上JDK 11也完全可行但一定要把JAVA_HOME指对不要在PATH里出现多个JDK版本互相干扰。装完之后先确认一下java -version echo $JAVA_HOME如果JAVA_HOME是空的即便java -version能跑Spark脚本也可能找不到JDK位置因为Spark的启动脚本主要靠JAVA_HOME变量定位Java运行环境。2.2 同步检查Hadoop配置YARN队列是重点Spark安装包的命名里写了hadoop3.2不代表你把Hadoop装好就万事大吉了。在开始搞Spark之前我强烈建议你先确认Hadoop侧的几个状态HDFS的NameNode和DataNode是否都正常启动了能用hdfs dfs -ls /看到文件系统。YARN的ResourceManager和NodeManager是否都活着能用yarn node -list看到节点状态。YARN队列是否存在默认队列名通常是default。如果你的集群配了Capacity Scheduler或者Fair Scheduler并且队列名不是default那么后面提交任务时要通过--queue指定队列名否则可能提交失败或者被拒绝。这些基础没确认好Spark装得再漂亮也白搭。尤其是YARN队列这个点很多人第一次用Spark on YARN的时候直接在命令里写--queue default但集群里根本没有这个队列名结果任务一直处于ACCEPTED状态既不跑也不报错非常坑。2.3 目录规划与用户权限还有一个容易被忽略的点安装目录和运行用户。我的实践习惯是把Spark安装在/opt/spark或者/usr/local/spark这样的统一目录下而不是放在用户家目录里。原因是Spark可能要被多个用户共用放家目录容易产生权限问题。另外启动Spark的用户最好对以下目录有绝对控制权Spark安装目录因为运行时会写work目录存放临时文件。HDFS上的临时目录通常是hdfs:///tmp提交任务的用户需要有写权限。YARN的本地目录通常是/usr/local/hadoop/tmp/nm-local-dir如果用默认路径注意检查磁盘空间是否够用。这些听起来琐碎但实际操作里我见过很多任务的失败根本不是代码问题而是某个临时目录没权限写入。提前做好目录规划后面能省一大半心。3. 解压、配置、跑通三十分钟完成Spark安装本体3.1 解压与环境变量不要小看这一步的小坑拿到spark-3.2.0-bin-hadoop3.2.tgz之后先上传到服务器然后解压到目标目录mkdir -p /opt/spark tar -zxvf spark-3.2.0-bin-hadoop3.2.tgz -C /opt/spark cd /opt/spark ln -s spark-3.2.0-bin-hadoop3.2 spark这里我习惯建一个spark软链接指向具体的版本目录。这样以后升级版本的时候只需要重新解压一个目录再改软链接Hadoop集成配置那些都不用动。接下来配置环境变量在/etc/profile或~/.bashrc里加上export SPARK_HOME/opt/spark/spark export PATH$SPARK_HOME/bin:$SPARK_HOME/sbin:$PATH为什么bin和sbin都要加因为日常提交任务、启动交互式Shell用的是bin下的spark-submit和spark-shell而启动独立集群模式时用的是sbin下的start-master.sh、start-worker.sh这些脚本。虽然你后面主要走YARN模式但本地测试时这些脚本还是很有用的。最常见的坑解压后直接运行spark-shell结果报SPARK_HOME not found或者java not found。前者往往是新开的终端没执行source /etc/profile后者则是JAVA_HOME没配好。这两个变量配好之前后面的一切都先停住。3.2 spark-env.sh与spark-defaults.conf的核心项Spark安装目录下的conf目录里有两份模板文件需要从头看起spark-env.sh.template和spark-defaults.conf.template。先把环境配置文件复制出来cp $SPARK_HOME/conf/spark-env.sh.template $SPARK_HOME/conf/spark-env.sh然后编辑至少要确认以下几点export JAVA_HOME/path/to/jdk export HADOOP_HOME/path/to/hadoop export HADOOP_CONF_DIR$HADOOP_HOME/etc/hadoop export SPARK_MASTER_HOSTyour-master-hostHADOOP_CONF_DIR是最容易被忽略的。在YARN模式下Spark需要从这个目录读取core-site.xml、hdfs-site.xml、yarn-site.xml才能拿到NameNode地址、ResourceManager地址等元信息。如果不配Spark根本无法知道该连哪个HDFS更别提提交到YARN了。接着复制默认配置cp $SPARK_HOME/conf/spark-defaults.conf.template $SPARK_HOME/conf/spark-defaults.conf这份文件里最核心的几个配置项是配置项建议值说明spark.masteryarn统一走YARN调度spark.eventLog.enabledtrue记录任务历史排查问题用spark.eventLog.dirhdfs:///spark-logs历史日志存到HDFSspark.sql.shuffle.partitions200默认shuffle分区数避免小文件过多3.3 用local模式跑一个数分析任务验证配置完成后先用最简单的local模式验证安装是否成功spark-shell --master local[2]如果能进入Scala交互界面说明Spark本体工作正常。退出后可以再用一个小任务验证计算能力看看 Spark 是否真的能跑起来spark-submit \ --master local[2] \ --class org.apache.spark.examples.SparkPi \ $SPARK_HOME/examples/jars/spark-examples_2.12-3.2.0.jar 10这个任务会计算圆周率跑通后输出类似Pi is roughly 3.141882的结果。到这里Spark本体安装就完成了但真正的挑战才刚刚开始——等你切到YARN模式去跑真实数据时各种资源分配的问题才会浮出水面。4. YARN模式CPU只用1个核资源不足问题的完整排查链路4.1 现象描述看似能用实则非常慢我之所以把这个单独拎出来讲是因为它在搜索引擎里几乎是Spark on YARN的第一高频问题集群明明每个节点十几二十个CPU核心但提交的任务每个Executor只用到1个vCore整个作业慢得离谱。我当初也被这个问题折腾得不轻。表面上看任务能跑、日志没有Fatal错误、YARN页面上也能看到Executor列表但你点开Executor详情就会发现每个Executor的vCore都是1。为什么4.2 root causeSpark Executor与YARN调度器如何交互要理解这个问题需要理清一个核心概念在Spark on YARN模式下一个Executor对应一个YARN Container而一个Container能拿到多少CPU和内存是由Spark提交时的参数决定的。跟CPU相关的关键参数是spark.executor.cores它决定了每个Executor内的Task并发度同时也决定了YARN为这个Container分配的vCore数量。关键坑点spark.executor.cores的默认值不同。在Standalone模式下这个值的默认行为比较宽松而在YARN模式下如果你不显式指定Spark默认会按 1 来处理Executor的CPU核心数。很多教程只教你指定--num-executors和--executor-memory却漏掉了--executor-cores所以问题就这么出来了。另外还有一个容易被忽视的层面YARN调度器本身也有限制。如果你是运维人员可以在capacity-scheduler.xml里看到yarn.scheduler.capacity.maximum-am-resource-percent这类配置也可以用yarn.scheduler.maximum-allocation-vcores来限制单个Container的最大vCore数。如果这个值写得很小比如4那么即便你在Spark提交参数里写了--executor-cores 8YARN也不会给你分配那么多最终Executor实际拿到的还是被YARN限制后的数量。4.3 从配置到验证一套可复现的修复方案我当时排查的完整链路大概是这样第一步先看YARN页面每个NodeManager上报的资源总量。如果ResourceManager页面显示的可用vCore总数和物理机的核心数对不上问题出在yarn-site.xml的yarn.nodemanager.resource.cpu-vcores。如果出问题机器有16核但YARN只认4核那就需要手动调整这个参数。第二步确认提交参数。如果你在命令里没写--executor-cores但spark-defaults.conf里也没配spark.executor.coresExecutor的默认CPU用量就是1。在测试集群里最直接的办法就是用spark-submit显式指定spark-submit \ --master yarn \ --deploy-mode cluster \ --name spark-yarn-test \ --num-executors 4 \ --executor-cores 4 \ --executor-memory 8g \ --class org.apache.spark.examples.SparkPi \ $SPARK_HOME/examples/jars/spark-examples_2.12-3.2.0.jar 100第三步任务跑起来后去YARN ResourceManager页面找到对应的Application点进去看Executors列表。如果每个Executor的vCore变成4了问题就解决了。我在实际中还遇到过一种变体资源明明够但是任务只有一两个Executor在跑其他Executor一直处于Pending。这种通常是YARN队列的最大资源配额不够或者任务申请的内存太大单个NodeManager塞不下。解决办法是调小--executor-memory和--executor-cores增加--num-executors让任务在现有资源下更碎片化地运行。还有一点不要忘了spark.executor.memoryOverhead。在YARN模式下Spark申请的内存是spark.executor.memory加上spark.executor.memoryOverhead后者默认是前者的10%左右最小384MB。如果你的executor内存给到了16g实际每个Container申请的内存其实是17.6g左右。很多同学卡在Container killed on request. Exit code is 143上就是没算这笔账。5. 关于log4j日志、内存模型和提交参数组合的经验5.1 using sparks default log4j profile到底是不是错误很多人第一次启动Spark看到这样一行日志using sparks default log4j profile: org/apache/spark/log4j-defaults.properties第一反应是这是不是哪里坏了其实不是。这只是Spark在明确的告诉你我没有在你设置的conf/log4j.properties里找到自定义日志配置所以我用内置的默认配置文件。如果你想让日志更干净、更可控可以按如下步骤操作cp $SPARK_HOME/conf/log4j.properties.template $SPARK_HOME/conf/log4j.properties然后编辑这份文件把log4j.rootCategoryINFO, console改成log4j.rootCategoryWARN, console这样再启动Spark那些INFO级别的中间日志就被过滤掉了只有WARN和ERROR才会打到屏幕上。排查问题时再临时改回INFO会清爽非常多。5.2 Spark内存模型拆解executor那点内存是怎么分的内存问题在热搜里也排得很靠前可见是另一大高频困惑。Spark在YARN模式下Executor的内存主要被分为三块执行内存Execution、存储内存Storage和用户代码内存User Memory。Execution内存负责shuffle、join、sort、aggregation这类的中间结果。task运行得快不快很大程度看这一块够不够。Storage内存负责缓存RDD、DataFrame、Broadcast变量等。cache之后想马上用但这块太小缓存会被频繁驱逐。User Memory用户代码本身占用的JVM对象、数据结构等这块不足通常表现为OOM。两个关键参数是spark.memory.fraction默认0.6表示Executor总内存的60%分给ExecutionStorage和spark.memory.storageFraction默认0.5表示Storage在统一内存里的保底份额。如果你发现任务频繁GC、缓存频繁失效应该优先调整这两个比例。比如shuffle重时把spark.memory.fraction调大一点给Execution更多空间缓存多时把spark.memory.storageFraction调高给Storage留足空间。5.3 一份我长期在用的Spark-submit参数模板最后分享一份我在生产环境长期使用的提交模板参数已经过多次实战验证spark-submit \ --master yarn \ --deploy-mode cluster \ --name ssp-daily-etl \ --queue etl \ --num-executors 10 \ --executor-cores 4 \ --executor-memory 8g \ --driver-memory 2g \ --conf spark.sql.shuffle.partitions400 \ --conf spark.default.parallelism400 \ --conf spark.yarn.executor.memoryOverhead1g \ --conf spark.serializerorg.apache.spark.serializer.KryoSerializer \ --conf spark.memory.storageFraction0.6 \ --conf spark.dynamicAllocation.enabledfalse \ --class com.example.datalake.ETLJob \ /data/app/spark-jobs/ssp-daily-etl.jar这套参数的几个要点num-executors定死不开启动态分配适合批处理场景下对资源有明确预期的作业shuffle.partitions和目标数据量挂钩我一般是最后落盘的目标文件数的3到5倍Kryo序列化器在复杂数据结构下比Java默认序列化器性能提升明显代价是某些类需要登记。如果你不确定自己的类是否兼容先在测试环境跑一版再上生产。根据我的经验资源参数不是越大越好。曾经我为了提高任务速度把单个Executor的cores调到8、内存调到32g结果不仅没有变快反而因为GC时间过长、任务调度不均整个作业比原来还慢。后来改成4核8g的小Executor、多开几个实例效果好了很多。这个现象也提醒我们Spark调优不是堆配置而是要去理解你的数据和计算特征。最后再分享一个小技巧spark.yarn.am.memory这个参数也值得关注一下。在YARN模式下AMApplicationMaster负责申请Container和管理任务状态它本身也需要内存。默认AM内存是512MB或1g如果你的作业特别大、Executor特别多AM可能先扛不住出现AM Container被杀的情况。适当调到2g或3g能减少一些莫名其妙的任务中途消失问题。本文还有配套的精品资源点击获取
返回列表