hive on spark 性能远比 hive on mr 要好,而且提供了一样的功能。用户的 SQL 无需修改就可以直接运行于 hive on spark ,udf 函数也是全部支持的。本文主要讲的是 hive on spark 在运行于 yarn 模式的情况下如何调优(下文举例讲解的 yarn 节点机器配置,假设有32核,120G 内存)
1 yarn 配置
yarn.nodemanager.resource.cpu-vcores 和 yarn.nodemanager.resource.memory-mb 这两个参数决定集群资源管理器能够有多少资源用于运行 yarn 上的任务。这两个参数的值是由机器的配置及同时在机器上运行的其它进程共同决定。本文假设仅有 hdfs 的datanode 和 yarn 的 nodemanager 运行于该节点。
1.1 配置cores
基本配置是 datanode 和 nodemanager 各一个核,操作系统两个核,然后剩下 28 核配置作为 yarn 资源,即 yarn.nodemanager.resource.cpu-vcores = 28
1.2 配置内存
对于内存,预留20GB给操作系统、datanode、nodemanager,剩余100GB作为 yarn 资源,即 yarn.nodemanager.resource.memory-mb = 100 * 1024
2 Spark配置
给 yarn 分配资源以后,那就要想着 spark 如何使用这些资源了,主要配置对象:
executor 和 driver 内存,executor配额,并行度。
2.1 executor内存
设置 executor 内存需要考虑如下因素:
- executor内存越多,越能为更多的查询提供 map join 的优化。由于垃圾回收的压力会导致开销增加
- 某些情况下 hdfs 的客户端不能很好地处理并发写入,所以过多的核心可能导致竞争
为了最大化使用core,建议将 core 设置为 4,5,6(多核心会导致并发问题,所以写代码的时候尤其是静态的链接等要考虑并发问题)具体分配核心数要结合 yarn 所提供的核心数。由于本文中涉及到的 node 节点是 28 核,那么很明显分配为 4 的话可以被整除。spark.executor.cores = 4,由于总共有 28 个核,那么最大可以申请的 executor 数是 7。总内存除以7,即100/7,可以得到每个executor约14GB内存。
spark.executor.memory 和 spark.executor.memoryOverhead 共同决定着 executor 内存。建议 spark.executor.memoryOverhead 占总内存的 15%~20% 。那么最终 spark.executor.memoryOverhead = 2G 和 spark.executor.memory = 12G
根据上面的配置,每个主机就可以申请 7 个 executor,每个 executor 可以运行 4 个任务,每个 core 一个 task。那么每个 task 的平均内存是 14/4 = 3.5GB。在 executor 运行的 task 共享内存,其实,executor 内部是用 newCachedThreadPool 运行task的。
确保 spark.executor.memoryOverhead 和 spark.executor.memory 的和不超过 yarn.scheduler.maximum-allocation-mb
2.2 driver内存
对于 driver 的内存配置,包括两个参数:
- spark.driver.memoryOverhead 每个driver能从 yarn 申请的堆外内存的大小
- spark.driver.memory 当运行 hive on spark 的时候,每个 spark driver 能申请的最大 jvm 堆内存。该参数结合 spark.driver.memoryOverhead 共同决定着 driver 的内存大小
driver的内存大小并不直接影响性能,但是也不要 job 的运行受限于 driver 的内存。这里给出 spark driver 内存申请的方案,假设 yarn.nodemanager.resource.memory-mb 是 X:
- 假设 X > 50GB,driver内存申请 12 GB
- 假设 12GB < X < 50GB,driver内存申请 4GB
- 假设 1GB < X < 12GB,driver内存申请 1GB
- 假设 X < 1GB,driver内存申请 256MB
这些数值是 spark.driver.memory 和 spark.driver.memoryOverhead 内存的总和,对外内存占总内存的 10%~15%。例如,假设 yarn.nodemanager.resource.memory-mb = 100 * 1024MB,那么driver 内存设置为 12GB,此时 spark.driver.memory = 10.5 GB 和 spark.driver.memoryOverhead = 1.5 GB
注意:资源多少直接对应的是数据量的大小,所以要结合资源和数据量进行适当缩减和增加
2.3 executor数
executor的数目是由每个节点运行的 executor 数目和集群的节点数共同决定。如果有 40 个节点,那么 hive 可以使用的最大 executor 数就是 280(40*7)。最大数目可能比这个小点,因为driver也会消耗 1core 和 12GB
Hive性能与用于运行查询的 executor 数量直接相关,通常性能与 executor 的数量成比例。例如,查询使用4个 executor 大约需要使用2个 executor 的一半时间。但是,性能在一定数量的 executor 中达到峰值,高于此值时,增加数量不会发送性能并且可能产生不利影响。
在大多数情况下,使用一半的集群容量(executor数量的一半)可以提供良好的性能。为了获得最佳性能,最好使用所有可用的 executor。例如,设置 spark.executor.instances = 280。对于基准测试和性能测量,强烈建议这样做。
2.4 动态 executor 申请
虽然将 spark.executor.instances 设置为最大值通常可以最大限度地提高性能,但不建议在多个用户运行 Hive 查询的生产环境中这样做。避免为用户会话分配固定数量的 executor,因为如果 executor 空闲,executor 不能被其它用户查询使用。在生产环境中,应该好好计划 executor 分配,以允许更多的资源共享。
Spark允许根据工作负载动态扩展分配给Spark应用程序的集群资源集。如要启用动态分配,请按照动态分配中的步骤进行操作;除了在某些情况下,强烈建议启用动态分配。
2.5 并行度
要让可用的 executor 得到充分利用,必须同时运行足够的任务(并行)。在大多数情况下,Hive 会自动确定并行度,但也可以在调优并发度方面有一些控制权。在输入端,map 任务的数量等于输入格式生成的 split 数。对于 Hive on Spark,输入格式为 CombineHiveInputFormat,它可以根据需要对基础输入格式生成的 split 进行分组,可以更好地控制 stage 边界的并行度。调整 hive.exec.reducers.bytes.per.reducer 以控制每个 reducer 处理的数据量,Hive 根据可用的 executor,执行程序内存,以及其它因素来确定最佳分区数。实验表时,只要生成足够的任务来保持所有可用的 executor 繁忙,Spark 就比 MapReduce 对 hive.exec.reducers.bytes.per.reducer 指定的值敏感度低。为获得最佳性能,为该属性选择一个值,以便 Hive 生成足够的任务以完全使用所有可用的 executor。
3 Hive 配置
Hive on Spark 共享了很多 hive 性能相关的配置,可以像调优 hive on mapreduce一样调优 hive on spark。然而,hive.auto.convert.join.noconditionaltask.size 是基于统计信息将基础 join 转化为 map join 的阀值,可能会对性能产生重大影响。尽管该配置可以用 hive on mr 和 hive on spark,但是两者的解释不同
数据的大小有两个统计指标:
- totalSize- 数量在磁盘上的近似大小
- rawDataSize- 数据在内存中的近似大小
hive on mr 用的是totalSize,hive on spark 使用的是rawDataSize。由于可能存在压缩和序列化,这两个值会有较大的差别。对于hive on spark 需要将 hive.auto.convert.join.noconditionaltask.size 指定为更大的值,才能将与 hive on mr 相同的 join 转化为 map join。
可以增加此参数的值,以使 map join 转换更好。将common join 转换为 map join 可以提高性能。如果此值设置得太大,则来自小表的数据将使用过多内存,任务可能会因内存不足而失败,须根据群集环境调整此值。
通过参数 hive.stats.collect.rawdatasize 可以控制是否收集 rawDataSize 统计信息。对于 hiveServer2 ,建议再配置两个额外的参数:hive.stats.fetch.column.stats = true 和 hive.optimize.index.filter = true
Hive性能调优通常建议使用以下属性:
hive.optimize.reducededuplication.min.reducer=4
hive.optimize.reducededuplication=true
hive.merge.mapfiles=true
hive.merge.mapredfiles=false
hive.merge.smallfiles.avgsize=16000000
hive.merge.size.per.task=256000000
hive.merge.sparkfiles=true
hive.auto.convert.join=true
hive.auto.convert.join.noconditionaltask=true
hive.auto.convert.join.noconditionaltask.size=20M(might need to increase for Spark, 200M)
hive.optimize.bucketmapjoin.sortedmerge=false
hive.map.aggr.hash.percentmemory=0.5
hive.map.aggr=true
hive.optimize.sort.dynamic.partition=false
hive.stats.autogather=true
hive.stats.fetch.column.stats=true
hive.compute.query.using.stats=true
hive.limit.pushdown.memory.usage=0.4 (MR and Spark)
hive.optimize.index.filter=true
hive.exec.reducers.bytes.per.reducer=67108864
hive.smbjoin.cache.rows=10000
hive.fetch.task.conversion=more
hive.fetch.task.conversion.threshold=1073741824
hive.optimize.ppd=true
4 预启动 yarn 容器
在开始新会话后提交第一个查询时,在查看查询开始之前可能会遇到稍长的延迟, 后续再次运行相同的查询,它的完成速度比第一个快得多。
Spark 执行程序需要额外的时间来启动和初始化 yarn 上的 Spark,这会导致较长的延迟。此外,Spark 不会等待所有 executor 在启动作业之前全部启动完成,因此在将作业提交到集群后,某些 executor 可能仍在启动。但是,对于在 Spark 上运行的作业,作业提交时可用 executor 的数量部分决定了 reducer 的数量。当就绪 executor 的数量未达到最大值时,作业可能没有最大并行度,这可能会进一步影响第一个查询的性能。
在用户较长期会话中,这个额外时间不会导致任何问题,因为它只在第一次查询执行时发生。然而,诸如 Oozie 发起的 Hive 工作之类的短期会画可能无法实现最佳性能。
为减少启动时间,可以在作业开始前启用容器预热。只有在请求的 executor 准备就绪时,作业才会开始运行,这样在 reduce 那一侧不会减少短会话的并行性。
要启用预热功能,请在发出查询之前将 hive.prewarm.enable 设置为 true,还可以通过设置 hive.prewarm.numcontainers 来设置容器数量。默认值为10
预热的 executor 的实际数量受 spark.executor.instances(静态分配)或 spark.dynamicAllocation.maxExecutors(动态分配)的值限制。hive.prewarm.numcontainers的值不应超过分配给用户会话的值。
注意:预热需要几秒钟,对于短会话来说是一个很好的做法,特别是如果查询涉及 reduce 阶段。但是,如果 hive.prewarm.numcontainers 的值高于集群中可用的值 ,则该过程最多可能需要30秒。需谨慎使用预热。
本文探讨Hive on Spark在YARN模式下的性能优化策略,包括YARN配置、Spark资源配置、Hive配置及预热容器等内容,旨在帮助用户充分利用集群资源,提升查询效率。
3233

被折叠的 条评论
为什么被折叠?



