你的 Hive 查询,还在用 MapReduce 跑吗?

如果你跟过我之前写的 Hive on MR 企业级调优 系列,应该对那种"调了半天参数,查询还是慢得像蜗牛"的无力感不陌生。MR 引擎的硬伤——每个 Stage 都要落盘、Shuffle 写一遍 HDFS、JVM 反复启停——不是靠调参能根治的。

换引擎,才是最大的调优。同样的 SQL 跑在 Hive on Spark 上,内存计算 + DAG 执行 + 更高效的 Shuffle,性能往往是数倍乃至一个数量级的提升——比如下面这条再普通不过的语句,换引擎前后的体感差距,可能比你调一个月参数还大:

SELECT dept, SUM(salary) FROM employee GROUP BY dept;

但"能跑"和"跑得好"之间,隔着一整套配置:

  • Yarn 给多少内存、多少核?给多了浪费,给少了 OOM。
  • 一个 Executor 分几核、多大内存?分错了要么资源碎片化,要么并行度上不去。
  • Driver 给多少?动态分配怎么设?Shuffle 服务要不要开?

这些参数环环相扣,一个配错,整个集群的资源利用率可能只有 30%。
这篇文章,我会从集群规划 → Yarn 配置 → Spark 配置,一层一层把资源账算清楚。不堆参数,而是告诉你每个数字背后的推导逻辑,把抽象的数字转化为看得见的运行现场。看完之后,面对任何规格的集群,你都能自己算出一套合理的配置。

本文是 【Hive on Spark 企业级调优】 系列第 1 篇,后续还有 SQL 优化(上 / 下)两篇,关注不迷路

1. 集群搭建规划

(1)集群节点类型分为Master节点类型和Worker节点类型。Master节点用于部署主从架构集群中的Master服务(主要负责任务的调度),Worker节点主要用于部署主从架构中的Slave服务(负责执行具体的业务)。

(2)例如:Master节点常用于部署HDFS的NameNode,Yarn的ResourcManager等角色,Worker节点常用于部署HDFS的DataNode,Yarn的NodeManager等角色。

(3)Master节点的配置可以不需要太高,但是Worker节点的配置需要尽可能的高,因为需要执行具体的任务,对资源的依赖较高。

(4)下面基于以下配置进行讲解

  • Master节点两台(主要是实现主从架构的高可用,排除单点故障),节点配置为16核CPU,64G内存
  • Worker节点三台,节点配置为32核CPU,128内存

2. Yarn配置

需要调整的Yarn参数均与CPU,内存等资源相关,核心配置如下:

2.1 Yarn配置说明

(1)yarn.nodemanager.resource.memory-mb

该参数是用于定义一个NodeManager节点可以使用的内存大小(分配给container使用的内存),单位为MB。该参数的配置,取决于NodeManager所在节点(服务器)的总内存容量和该节点运行其他服务的数量。

NodeManager服务部署在Worker节点中,Worker节点的内存大小为128G,该节点还需要运行其他的服务,所以综合下来,可以设置该参数为64G。

<property>
    <name>yarn.nodemanager.resource.memory-mb</name>
    <value>65536</value>
</property>

补充:

# 如何判定参数是否给的合适,可以查看服务器运行时的剩余内存容量
free -h
关注 available 列而非 free 列,因为 Linux 会利用空闲内存做缓存。

(2)yarn.nodemanager.resource.cpu-vcores

该参数是用于定义一个NodeManager节点可以使用的CPU核数(分配给container使用)。该参数的配置同样需要考虑NodeManger所在节点的CPU核数和该节点运行的其他服务。

NodeManager服务部署在Worker节点中,Worker节点的CPU总核数32核,该节点还需要运行其他的服务。所以综合下来,可以分配一半的CPU核数给NodeManager节点,将参数设置为 16 核。

<property>
    <name>yarn.nodemanager.resource.cpu-vcores</name>
    <value>16</value>
</property>

(3)yarn.scheduler.maximum-allocation-mb

该参数用于定义单个container能够使用的最大内存。注意:由于Spark on Yarn模式下,Spark的 Driver 和 Executor 都运行在container中,故该参数不能小于 Driver 和 Executor 的内存配置。推荐配置为16G,既一个NodeManager最少可以同时启动 4 个container。

<property>
    <name>yarn.scheduler.maximum-allocation-mb</name>
    <value>16384</value>
</property>

(4)yarn.scheduler.minimum-allocation-mb

该参数含义:单个container能够使用的最小内存,推荐配置如下

<property>
    <name>yarn.scheduler.minimum-allocation-mb</name>
    <value>512</value>
</property>

2.2 Yarn配置实操

(1)修改$HADOOP_HOME/etc/hadoop/yarn-site.xml文件

(2)修改如下参数

<property>
    <name>yarn.nodemanager.resource.memory-mb</name>
    <value>65536</value>
</property>
<property>
    <name>yarn.nodemanager.resource.cpu-vcores</name>
    <value>16</value>
</property>
<property>
    <name>yarn.scheduler.maximum-allocation-mb</name>
    <value>16384</value>
</property>
<property>
    <name>yarn.scheduler.minimum-allocation-mb</name>
    <value>512</value>
</property>

(3)分发该配置文件

(4)重启yarn

Yarn集群的总资源大小

​ 内存:64 * 3 = 192G

​ CPU:16 * 3 = 48核

3. Spark配置

3.1 Executor配置说明

3.1.1 spark.executor.cores

​ 单个Executor的CPU核数,由spark.executor.cores参数决定,建议配置为 4-6,但是具体配置多少,还要视情况而定,原则就是充分的利用资源。

​ 此处单个NodeManager节点共有16核CPU提供给Executor使用,则spark.executor.core的配置为4最合适。因为如果配置5个,则只能启动三个executor节点,浪费1个CPU。6个同理,启动2个节点,浪费4个CPU。

3.1.2 spark.executor.memory & spark.executor.memoryOverhead

​ Spark On Yarn模式下的 Executor 内存模型:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

​ Executor内存相关的参数有:spark.executor.memoryOverhead 和 spark.executor.memory。

spark.executor.memory用于指定Executor进程的堆内存,用于任务的计算和存储。spark.executor.memoryOverhead用于指定Executor进程堆外内存,用于JVM的额外开销,操作系统的开销等。两者之和才是一个Executor进程所需的总内存大小。默认情况下:spark.executor.memory = spark.executor.memoryOverhead * 10。

​ 以上两个参数推荐的配置思路:根据单个NodeManager的核数和单个Executor的核数,计算出每个NodeManager节点最多能够运行多少个Executor。再将NodeManager的总内存平均分配给每个Executor,最后将单个Executor的内存按照大概10:1的比例分配给spark.executor.memory和spark.executor.memoryOverhead。

​ 根据以上思路:(spark.executor.memory + spark.executor.memoryOverhead)= 64G / 4 = 16G

综上所述:

spark.executor.memory	14G,
spark.executor.memoryOverhead	2G

3.1.3 Executor个数配置

​ Executor个数指分配给一个Spark应用的Executor个数,Executor的个数对于Spark应用的执行速度有很大的影响,如何能够合理的利用资源很重要。

​ 一个spark应用的Executor个数指定方式用两种:**静态分配 **和 动态分配

  • 静态分配

可以通过 spark.executor.instances 参数指定一个spark应用启动的Executor个数。

缺点:这种方式需要自行估计每个spark应用所需要的资源,并为每个spark应用单独配置Executor个数。

  • 动态分配

可以根据一个spark应用的工作负载,动态的调整Executor的个数。这意味着一个spark应用周期运行过程中,需要时可以申请更多的资源,不需要时可以释放空闲的资源。

在生产环境中,推荐使用动态分配。动态分配相关参数

# 启动动态分配
spark.dynamicAllocation.enabled		true
# 启动 Spark Shuffle 服务
spark.shuffle.service.enabled 		true
# 设置Executor初始个数
spark.dynamicAllocation.initialExecutors	1
# 设置Executor最小个数
spark.dynamicAllocation.minExecutors		1
# 设置Executor最大个数 yarn集群总核数(48) / Executor核数(4)
spark.dynamicAllocation.maxExecutors		12
# Executor空闲时长,若某个Executor空闲时间超过这个值,就会关闭
spark.dynamicAllocation.executorIdleTimeout		60s
# 积压任务等待时长,若Task等待时间超过次值,则会申请新的Executor
spark.dynamicAllocation.schedulerBacklogTimeout		10s

关于第二个参数启动shuffle,shuffle服务的作用是管理Executor中的各Task输出文件,主要是shuffle过程中map端的输出文件。由于启动了动态分配,spark会在一个应用未结束前,将已经完成任务,超出空闲时间的Executor关闭。Executor关闭之后,其输出的文件没有办法共其他Executor使用。需要启动Spark Shuffle服务,来管理各Executor输出的文件,这样关闭空闲的Executor不会影响后续的计算任务。

3.2 Driver配置说明

Driver主要配置内存即可,相关的参数用spark.driver.memory 和 spark.driver.memoryOverhead。与Executor的内存配置类似。

spark.driver.memory 用于指定 Driver 进程堆内存大小。spark.driver.memoryOverhead 用于指定Driver进程的堆外内存大小,默认情况下: spark.driver.memory = spark.driver.memoryOverhead * 10,两者之和才是一个Driver进程的总内存大小。

假设yarn.nodemanager.resource.memory-mb 为 X,常规情况下:

​ X > 50G —— Driver可设置为12G

​ 12G < X < 50G —— Driver可设置为 4G

​ 1G < X < 12G —— Driver可设置为1G

此处 yarn.nodemanager.resource.memory-mb 为64G,则可以给Driver分配12G。

综上所述:

spark.driver.memory 	10G
spark.driver.memoryOverhead		2G

3.3 Spark配置实操

(1)修改$HIVE_HOME/conf/spark-defaults.conf

# 运行模式
spark.master		yarn
# 开启日志服务
spark.eventLog.enabled		true
# 日志文件地址(HDFS)
spark.eventLog.dir			hdfs://NameNode地址(active)/spark-history
# executor核数
spark.executor.cores		4
# executor 堆内存
spark.executor.memory		14g
# executor 堆外内存
spark.executor.memoryOverhead	2g
# driver 堆内存
spark.driver.memory		10g
# driver 堆外内存
spark.driver.memoryOverhead	2g
# 启动动态分配
spark.dynamicAllocation.enabled		true
# 启动 Spark Shuffle 服务
spark.shuffle.service.enabled 		true
# 设置Executor初始个数
spark.dynamicAllocation.initialExecutors	1
# 设置Executor最小个数
spark.dynamicAllocation.minExecutors		1
# 设置Executor最大个数 yarn集群总核数(48) / Executor核数(4)
spark.dynamicAllocation.maxExecutors		12
# Executor空闲时长,若某个Executor空闲时间超过这个值,就会关闭
spark.dynamicAllocation.executorIdleTimeout		60s
# 积压任务等待时长,若Task等待时间超过次值,则会申请新的Executor
spark.dynamicAllocation.schedulerBacklogTimeout		10s

(2)配置 Spark Shuffle 服务

​ Spark Shuffle服务配置因Cluster Manager(standalone, Mesos, Yarn)的不同配置不一样。此处以Yan作为Cluster Manager。

​ (1)拷贝 $SPARK_HOME/yarn/spark-3.3.1-yarn-shuffle.jar 到 $HADOOP_HOME/share/hadoop/yarn/lib

​ (2)分发 $HADOOP_HOME/share/hadoop/yarn/lib/spark-3.3.1-yarn-shuffle.jar

(3)修改$HADOOP_HOME/etc/hadoop/yarn-site.xml 文件

<!-- 开启 Spark Shuffle 服务 -->
<property>
    <name>yarn.nodemanager.aux-services</name>
    <value>mapreduce_shuffle,spark_shuffle</value>
</property>
<!-- 执行 Spark Shuffle 类名 -->
<property>
    <name>yarn.nodemanager.aux-services.spark_shuffle.class</name>
    <value>org.apache.spark.network.yarn.YarnShuffleService</value>
</property>

(4)分发 $HADOOP_HOME/etc/hadoop/yarn-site.xml 配置文件

(5)重启 Yarn

3.4 Spark On Yarn总结

​ Spark on Yarn 的本质是:Spark 将自身的资源管理委托给 Yarn。Driver 负责"做什么"(DAG 调度),Yarn 的 ResourceManager + NodeManager 负责"在哪做"(Container 分配),Executor 负责"怎么做"(Task 执行)。三者通过 Container 这一抽象解耦,使得 Spark 无需自己管理集群资源,专注于计算逻辑。

3.4.1 Spark On Yarn 组件映射

Spark 组件运行位置说明
DriverCluster模式:Yarn 集群中的 Container
Client模式:提交机器本地
负责创建 SparkContext、解析 DAG、调度 Task
ApplicationMaster (AM)Yarn 集群中的 Container负责向 RM 申请资源、管理 Executor 生命周期
ExecutorWorker 节点的 Container 中负责执行具体 Task、缓存 RDD 数据
TaskExecutor 内部的线程实际计算单元(map/reduce 等)

3.4.2 Spark On Yarn 流程

在这里插入图片描述

3.4.2 资源分配关系

Yarn 集群总资源
├── 内存:64G × 3 Worker = 192G
└── CPU :16核 × 3 Worker = 48核

单个 Worker 节点 (NodeManager) 可分配资源
├── 内存:64G(yarn.nodemanager.resource.memory-mb = 65536)
└── CPU :16核(yarn.nodemanager.resource.cpu-vcores = 16)

单个 Executor 占用资源
├── CPU :4核(spark.executor.cores = 4)
├── 堆内存:14G(spark.executor.memory)
└── 堆外内存:2G(spark.executor.memoryOverhead)
    → 合计 16G = 单个 Container 最大内存(yarn.scheduler.maximum-allocation-mb)

每个 Worker 可运行 Executor 数
├── 按 CPU:16核 ÷ 4核 = 4 个
└── 按内存:64G ÷ 16G = 4 个
    → 每个 Worker 最多 4 个 Executor

集群最大 Executor 数(动态分配上限)
└── 48核 ÷ 4核 = 12 个(spark.dynamicAllocation.maxExecutors = 12)

Driver 占用资源(独占一个 Container)
├── 堆内存:10G
└── 堆外内存:2G
    → 合计 12G

更多推荐