我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Spark内存调优指南

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Spark内存调优指南

引言

本文是关于Spark优化性能与内存使用的最佳实践,翻译整理自Tuning - Spark 3.3.2 Documentation。由于spark内存计算的特性,很多因素都会影响Spark的表现:CPU、网络带宽或者内存。一般来说,数据可以全部装入内存,则带宽是瓶颈;有时你需要进行调优,主要是两个方面:数据序列化和内存使用。

数据序列化

在分布式应用中数据序列化扮演着至关重要的角色。序列化对象的速度很慢,或者消耗大量字节的格式,会大大降低计算速度。通常情况下,这将是你优化Spark应用时首先要调整的东西。Spark的目标是在易用性(允许你在操作中使用任何Java类型)和性能之间取得平衡。它提供了两个序列化库:

Java serialization:默认是这个,Java序列化很灵活,但往往相当慢,而且导致许多类的序列化格式很大。

Kryo serialization:Spark也可以使用Kyro库更快地序列化对象。Kryo明显比Java序列化更快、更紧凑(通常高达10倍),但不支持所有的Serializable类型,并要求你提前注册你将在程序中使用的类以获得最佳性能。

使用Kryo注册并不是想象中十分晦涩难懂的操作,多数情况仅需一行代码就行!

可以在 SparkConf 里设置conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")来初始化Kryo。建议在网络密集型应用里使用Kyro序列化。从Spark2.0开始,在Shuffle RDD阶段的一些简单类型已经自动使用了Kyro序列化。

想要注册自定义类使用Kyro,只需如下操作:

val conf = new SparkConf().setMaster(...)......
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
val sc = new SparkContext(conf)

如果代码对象很大,你需要增大spark.kryoserializer.buffer配置。如果你没有注册你的自定义类,Kryo仍然会生效,但是它不得不随对象存储全类名,这很浪费资源。

内存调优

这部分将首先概述Spark的内存管理,然后讨论用户可以采取的具体策略,以便在我们的应用程序中更有效地利用内存。特别是,我们将描述如何确定你的对象的内存使用情况,以及如何通过改变你的数据结构改善它,或通过以序列化的格式存储数据。然后,我们将介绍调整Spark的缓存大小和Java的垃圾收集器。

内存管理概述

众所周知的是Spark的内存主要分为2大块:执行与存储(execution and storage)。执行内存就是计算用的,如shuffle/join/sort这些,存储内存则用于缓存和跨集群传递数据。在Spark中这两块内存是统一区域管理的,名为M。当没有执行内存需求时,存储内存可以获取全部内存,反之亦然。执行可以在必要时驱逐存储占用的内存空间,直到存储内存占用降低至某一界限R。换言之,R描述了M中的一个子区域,其中缓存的块永远不会被驱逐。由于执行中的复杂性,存储可能不会驱逐执行内存。

这种设计确保了几个理想的特性。首先,不使用缓存的应用程序可以使用整个空间来执行,避免了不必要的磁盘溢出。其次,使用缓存的应用程序可以保留一个最小的存储空间(R),其数据块不会被驱逐。最后,这种方法为各种工作负载提供了合理的开箱即用的性能,而不需要用户对内存的内部划分有专业认识。

虽然有两个相关的配置,但一般用户应该不需要调整,因为默认值适用于大多数工作负载。

spark.memory.fraction将M的大小表示为(JVM堆空间-300MB)的一部分(默认为0.6)。其余的空间(40%)被保留给用户数据结构、Spark的内部元数据,以及在记录稀少和异常大的情况下对OOM错误的保护。

spark.memory.storageFraction表示R占M多大一部分(默认为0.5)。R是M中的存储空间,其中的缓存块对执行的驱逐免疫。

确定内存消耗

并没有一个放之四海而皆准的公式告诉你RDD占用了多少内存,对一个具体业务需要实践出真知。

确定一个数据集所需的内存消耗量的最佳方法是创建一个RDD,将其放入缓存,并查看Web UI中的 "Storage "页面。该页面将告诉你该RDD占用了多少内存。

要估计一个特定对象的内存消耗,可以使用SizeEstimator’s estimate 方法。这对于试验不同的数据布局以修整内存使用量,以及确定一个广播变量在每个执行器堆上所占用的空间是很有用的。

调整数据结构

减少内存消耗的第一个方法是避免那些增加开销的Java特性,如基于指针的数据结构和包装对象。有几种方法可以做到这一点。

  • 将你的数据结构设计成倾向于对象的数组和原始类型,而不是标准的Java或Scala集合类(例如HashMap)fastutil库为原始类型提供了方便的集合类,与Java标准库兼容。
  • 尽可能避免使用带有大量小对象和指针的嵌套结构。
  • 考虑使用数字ID或枚举对象而不是字符串作为键。
  • 如果你的RAM少于32GiB,设置JVM标志-XX:+UseCompressedOops,使指针为四字节而不是八字节。你可以在 spark-env.sh 中添加这些选项。

RDD序列化存储

当你的对象仍然太大,无法有效地存储,尽管有这样的调整,减少内存使用的一个更简单的方法是以序列化的形式存储它们,使用RDD持久化API中的序列化存储级别,如MEMORY_ONLY_SER。然后,Spark将把每个RDD分区存储为一个大的字节数组。以序列化形式存储数据的唯一缺点是访问时间较慢,因为必须在运行中对每个对象进行反序列化。如果你想以序列化的形式缓存数据,我们强烈建议你使用Kryo,因为它导致的大小比Java序列化小得多(当然也比原始Java对象小)。

GC的调整

当你的程序所存储的RDD有很大的 "流失 "时,JVM的垃圾回收可能是一个问题。(在只读取一次RDD,然后对其进行许多操作的程序中,这通常不是一个问题)。当Java需要驱逐旧对象为新对象腾出空间时,它需要追踪你所有的Java对象并找到未使用的对象。这里需要记住的要点是,垃圾收集的成本与Java对象的数量成正比,所以使用对象较少的数据结构(例如,用Ints数组代替LinkedList)可以大大降低这一成本。一个更好的方法是以序列化的形式持久化对象,如上所述:现在每个RDD分区将只有一个对象(一个字节数组)。在尝试其他技术之前,如果GC是一个问题,首先要尝试的是使用序列化的缓存。

由于你的任务的工作内存(运行任务所需的空间量)和你的节点上缓存的RDD之间的干扰,GC也可能成为一个问题。我们将讨论如何控制分配给RDD缓存的空间以缓解这一问题。

测量GC的影响

GC调整的第一步是收集关于垃圾收集发生频率和花费在GC上的时间的统计数据。这可以通过在Java选项中添加-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps来实现。下次运行Spark作业时,你会看到每次发生GC时,工作节点的日志中都会打印出信息。请注意,这些日志将出现在集群的工作节点上(在其工作目录的stdout文件中),而不是在你的驱动程序上。

高级GC调优

为了进一步调整GC,我们首先需要了解一些关于JVM中内存管理的基本信息。

Java的堆空间被分为两个区域 Young 和 Old。Young代是用来存放临时的对象的,而Old代是用来存放寿命较长的对象的。

年轻一代又被划分为三个区域[Eden, Survivor1, Survivor2]。

对GC行为的简化描述:当Eden满时,在Eden上运行一个小的GC,Eden和Survivor1中活着的对象被复制到Survivor2。Survivor区域被交换。如果一个对象足够老或者Survivor2已经满了,它就会被移到Old。最后,当Old接近满的时候,一个Full GC被调用。

Spark中GC调整的目标是确保只有长期存在的RDD被存储在Old一代,而Young一代有足够的大小来存储短期对象。这将有助于避免全面GC来清理任务执行过程中创建的临时对象。一些可能有用的步骤是。

  • 通过收集GC统计信息,检查是否有太多的垃圾收集。如果在一个任务完成之前多次调用Full GC,这意味着没有足够的内存可用于执行任务。
  • 如果有太多的小GC但没有太多的大GC,为Eden分配更多的内存会有帮助。你可以将Eden的大小设置为对每个任务所需内存的高估值。如果Eden的大小被确定为E,那么你可以使用选项-Xmn=4/3*E来设置Young generation的大小。(按4/3的比例增加是为了考虑幸存者区域所使用的空间)。
  • 在打印的GC统计中,如果OldGen接近满了,通过降低spark.memory.fraction来减少用于缓存的内存量;缓存更少的对象比减缓任务的执行要好。另外,也可以考虑减少Young代的大小。这意味着降低-Xmn,如果你已经如上设置。如果没有,可以尝试改变JVM的NewRatio参数的值。许多JVM将其默认为2,这意味着老一代占据了2/3的堆。它应该足够大,以至于这个分数超过了spark.memory.fraction
  • 尝试设置-XX:+UseG1GC来使用G1GC垃圾收集器。在垃圾收集是一个瓶颈的情况下,它可以提高性能。注意,对于大的执行器堆大小,用-XX:G1HeapRegionSize增加G1区域大小可能很重要。
  • 举个例子,如果你的任务是从HDFS读取数据,任务使用的内存量可以用从HDFS读取的数据块的大小来估计。请注意,解压后的块的大小往往是块的2或3倍。因此,如果我们希望有3或4个任务的工作空间,而HDFS块的大小是128MiB,我们可以估计Eden的大小是43128MiB。
  • 监控垃圾收集的频率和时间在新的设置下如何变化。

我们的经验表明,GC调整的效果取决于你的应用程序和可用的内存量。网上还描述了许多调优选项,但在高层次上,管理完全GC发生的频率可以帮助减少开销。

可以通过在作业的配置中设置 spark.executor.defaultJavaOptions 或 spark.executor.extraJavaOptions 来指定执行器的 GC 调整设置。

其他考虑因素

并行度水平

除非你把每个操作的并行度设置得足够高,否则集群不会得到充分的利用。Spark会根据文件的大小自动设置在每个文件上运行的 map 任务的数量(当然你可以通过SparkContext.textFile等的可选参数来控制),而对于分布式的 "reduce "操作,比如groupByKeyreduceByKey,它会使用最大的父RDD的分区数量。你可以把并行程度作为第二个参数传递(见spark.PairRDDFunctions文档),或者设置配置属性spark.default.parallelism来改变默认值。一般来说,我们建议在你的集群中每个CPU核有2-3个任务。

输入路径上的并行Listing

有时,当作业输入有大量的目录时,你可能还需要增加目录列表的并行性,否则这个过程可能会花费很长的时间,特别是在针对S3这样的对象存储时。如果你的作业在具有Hadoop输入格式的RDD上工作(例如,通过SparkContext.sequenceFile),则通过spark.hadoop.mapreduce.input.fileinputformat.list-status.num-reads(目前默认为1)控制并行性。

对于具有基于文件的数据源的Spark SQL,你可以调整spark.sql.sources.parallelPartitionDiscovery.threshold和spark.sql.sources.parallelPartitionDiscovery.parallelism,以提高列举并行性。更多细节请参考Spark SQL性能调优指南。

Reduce任务的内存使用情况

有时,你会得到OutOfMemoryError,不是因为你的RDDs不适合在内存中,而是因为你的某个任务的工作集,比如groupByKey中的一个Reduce任务太大。Spark的shuffle操作(sortByKey、groupByKey、reduceByKey、join等)在每个任务中建立一个哈希表来执行分组,而这个哈希表往往会很大。这里最简单的解决方法是提高并行化水平,使每个任务的输入集更小。Spark可以有效地支持短至200毫秒的任务,因为它在许多任务中重复使用一个执行器JVM,而且它的任务启动成本很低,所以你可以安全地将并行化水平提高到超过集群中的核心数量。

广播大型变量

使用SparkContext中的广播功能可以大大减少每个序列化任务的大小,以及在集群中启动作业的成本。如果你的任务中使用了驱动程序中的任何大型对象(例如静态查询表),可以考虑将其变成一个广播变量。Spark在主程序上打印每个任务的序列化大小,所以你可以看一下,以决定你的任务是否太大;一般来说,大于20KiB的任务可能值得优化。

数据位置

数据位置可以对Spark作业的性能产生重大影响。如果数据和对其进行操作的代码在一起,那么计算往往会很快。但如果代码和数据是分开的,一个必须移动到另一个。通常情况下,将序列化的代码从一个地方运送到另一个地方要比运送一大块数据快,因为代码的大小比数据小得多。Spark围绕这个数据定位的一般原则建立了它的调度。

数据定位是指数据离处理它的代码有多近。根据数据的当前位置,有几个级别的定位。按照从最近到最远的顺序:PROCESS_LOCAL、NODE_LOCAL、NO_PREF、RACK_LOCAL、ANY。

Spark通常的做法是等待一下,希望有一个繁忙的CPU腾出手来。一旦超时,它就开始把数据从远处移到空闲的CPU上。每个级别之间的回退等待超时可以单独配置,也可以在一个参数中全部配置;详见spark.locality参数。如果你的任务很长,看到的定位性很差,你应该增加这些设置,但默认值通常很好用。

小结

这份简短指南指出了你在调整Spark应用程序时应该知道的主要问题——最重要的是数据序列化和内存调整。对于大多数程序来说,切换到Kryo序列化并以序列化的形式持久化数据将解决大多数常见的性能问题。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Spark内存调优指南

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Spark内存调优指南

这篇文章主要为大家介绍了Spark内存调优指南数据序列化分析详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-07

Spark调优指南

Spark相关问题Spark比MR快的原因?1) Spark的计算结果可以放入内存,支持基于内存的迭代,MR不支持。2) Spark有DAG有向无环图,可以实现pipeline的计算模式。3) 资源调度模式:Spark粗粒度资源调度,MR是细粒度资源调度。资源
Spark调优指南
2018-12-04

Spark内存调优的方法是什么

这篇文章主要介绍“Spark内存调优的方法是什么”,在日常操作中,相信很多人在Spark内存调优的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Spark内存调优的方法是什么”的疑惑有所帮助!接下来
2023-07-05

Spark性能优化指南——初级篇

原文来我的公众号:Spark性能优化指南——初级篇一. Spark作业原理我们使用spark-submit提交一个Spark作业之后,这个作业就会启动一个对应的Driver进程。该进程是向集群管理器(Yarn,K8s)申请运行Spark作业需要使用的资源,这里
Spark性能优化指南——初级篇
2021-09-09

Golang性能调优指南

Golang 是一种由 Google 开发的开源编程语言,以其简洁、高效的特点受到许多开发者的青睐。然而,在开发过程中,为了保证程序的性能和效率,我们有时需要对代码进行调优。本文将介绍一些 Golang 性能调优的技巧,并提供具体的代码示例
Golang性能调优指南
2024-03-06

Spark在Ubuntu上的内存管理优化

在Ubuntu上使用Apache Spark时,内存管理是一个关键的性能考量因素。以下是一些优化Spark内存管理的建议:调整Spark配置参数:spark.executor.memory:控制每个执行器(executor)的内存量。sp
Spark在Ubuntu上的内存管理优化
2024-10-22

C++技术中的调试:内存问题侦查与修复指南

c++++ 技术中的内存问题可通过 gdb、valgrind 和 addresssanitizer 侦查与修复。使用 gdb 可查找段错误,valgrind 可检测内存泄漏,而 addresssanitizer 则可侦测缓冲区溢出和指针错误
C++技术中的调试:内存问题侦查与修复指南
2024-05-07

Flink 流式聚合性能调优指南

原文:Flink 流式聚合性能调优指南SQL 是数据分析中使用最广泛的语言。Flink Table API 和 SQL 使用户能够以更少的时间和精力定义高效的流分析应用程序。此外,Flink Table API 和 SQL 是高效优化过的,它集成了许多查询优化
Flink 流式聚合性能调优指南
2017-11-03

Go语言应用性能调优指南

Go 语言应用性能调优指南在生产环境中优化 Go 应用程序的性能对于确保其平稳运行和用户满意度至关重要。本文将提供一份全面的指南,涵盖性能调优的最佳实践、工具和实战案例。最佳实践使用 Go 内置的性能分析工具: pprof 和 trac
Go语言应用性能调优指南
2024-05-07

PHP底层系统性能调优指南

PHP底层系统性能调优指南概述:随着Web应用程序的发展,PHP已经成为最受欢迎的服务器端脚本语言之一。然而,PHP在处理大型、高流量的应用程序时可能会面临性能瓶颈。本文将为你提供一些调优技巧和具体的代码示例,帮助你优化PHP底层系统性能。
PHP底层系统性能调优指南
2023-11-08

Go函数性能优化实战指南:内存管理技巧

优化 go 函数内存性能的技巧:使用内存池优化内存分配;重用对象和使用切片减少分配;使用 mmap 提高大文件处理性能。Go 函数性能优化实战指南:内存管理技巧Go 的内存管理机制被称为垃圾回收,它以自动回收不再使用的内存而闻名。然而,在
Go函数性能优化实战指南:内存管理技巧
2024-05-03

分布式Golang API的性能调优指南

优化分布式 golang api 性能的指南:使用协程:协程可以并行执行任务,提高吞吐量和降低延迟。使用 channel:channel 用于协程通信,同步任务和避免锁竞争。缓存响应:缓存可以减少对后端服务的调用,提高性能。案例:通过使用协
分布式Golang API的性能调优指南
2024-05-08

JVM内存调优有哪些技巧

这篇文章给大家介绍JVM内存调优有哪些技巧,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。这里向大家描述一下JVM内存的设置原理及调优,默认的java虚拟机的大小比较小,在对大数据进行处理时java就会报错:java.l
2023-06-17

JVM 内存调优,你学会了吗?

初始堆大小(-Xms)和最大堆大小(-Xmx):根据应用程序的需求和可用系统内存,合理设置堆的初始大小和最大大小。初始大小应根据应用程序的启动需求,最大大小应根据应用程序的内存需求进行设置。
JVM内存调优2024-11-30

超强!机器学习超参数调优指南

超参数调优是机器学习和深度学习中重要的步骤,旨在选择最佳的超参数组合,以提高模型的性能。

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录