spark2.x-官方调优指南(翻译)

原文

http://spark.apache.org/docs/latest/tuning.html

序列化

序列化在任何分布式应用程序的性能中起着重要的作用。
将对象序列化到或消耗大量字节的格式将大大减慢计算速度。spark 提供了两个序列化库:

Java serialization:
默认情况下,spark使用java对象序列化机制
https://docs.oracle.com/javase/6/docs/api/java/io/Serializable.html

Kryo serialization
还可以使用KRYO库(版本2)来更快地序列化对象。kryo是更快,更紧凑,比java序列化(通常为10倍),但不支持所有可序列化的类型和需要注册类。
https://github.com/EsotericSoftware/kryo

conf.set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”).
此设置配置用于不仅用于worker节点之间shuffle,而且可以序列化RDDs到磁盘。kryo不是默认的唯一原因是自定义注册要求,但是我们建议在任何网络密集型应用程序中尝试它。Skas2.0以后,我们内部使用KRYO序列化器在简单类型、简单类型数组或字符串类型的RDD的shuffle 过程中。
spark自动包含KRYO序列化器,使用的许多常用核心Scala类覆盖Twitter chill的AlcalcalGISTAR。

使用方法
val conf = new SparkConf().setMaster(…).setAppName(…)
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
val sc = new SparkContext(conf)

如果你的对象很大,你可能还需要增加spark.kryoserializer.buffer config. 这个值需要足够大,以容纳您要序列化的最大对象。
最后,如果不注册自定义类,KRYO将仍然工作,但它必须用每个对象存储完整的类名,这是浪费的。

调整内存使用有三个方面的考虑:
对象使用的内存量(您可能希望整个数据集适合内存)、
访问这些对象的成本以及垃圾收集的开销(如果在对象方面有较高的周转率)。

默认情况下,java对象的快速访问,但可以很容易地消耗因素的2-5倍更多的空间比原始数据在他们的领域。这是由于以下几个原因:

每一个java对象的“对象头”,大约是16个字节,包含的信息,如一个指针指向它的类。对于一个数据很少的对象(比如一个int字段),这可能比数据大。

Java的字符串有约40个字节的开销,在原字符串数据(因为他们在一个大的网络chars阵列和保持额外的数据,如长度),和大的双字节字符为每个字符串的内部使用UTF-16编码。因此,10个字符的字符串,可以轻易地消费60字节。

常见的集合类,如 HashMap and LinkedList, ,使用链接的数据结构,其中每个条目都有一个“包装器”对象(例如MAP.WITH)。这个对象不仅有一个报头,而且还有指针(通常是每个字节8个)到列表中的下一个对象。

集合存储经常用到”装箱”的值类型 如java.lang.integer”对象。

内存管理器

SARK中的内存使用主要属于两类之一:执行和存储。
执行内存指的是用于shuffles、joins、sorts和aggregations的计算,
而存储存储器则是指用于缓存和传播跨集群的内部数据。
执行和存储共享一个统一的区域(M)。
当不使用执行内存时,存储可以获取所有可用内存,反之亦然。

执行可以在必要时驱逐存储,但仅在总存储内存使用率低于某个阈值(R)之前。
换句话说,R描述了M内的一个子区域,其中cached blocks 从不被驱逐。由于执行的复杂性,存储可能不会放弃执行。

这种设计保证了几个理想的性能。
首先,不使用缓存的应用程序可以使用整个空间执行,避免不必要的磁盘溢出。
第二,使用缓存的应用程序可以保留最小的存储空间(R),其中它们的数据块免于被驱逐。
最后,这种方法为各种工作负载提供了合理的开箱即用性能,而不需要用户对内存如何被内部划分的专门知识。

虽然有两个相关的配置,但典型用户不需要调整它们,因为默认值适用于大多数工作负载:

spark.memory.fraction将M的大小表示为(JVM堆空间-300 MB)的一小部分(默认值0.6)。其余的空间(40%)被保留用于用户数据结构、spark中的内部元数据,并且在稀疏和异常大的记录的情况下防止OOM错误。

spark.memory.fraction应该设置JVM的旧的或“永久的”一代中舒适地适应这个堆空间

spark.memory.storageFraction 表示R的大小为M的一部分(默认值0.5)。R是M内的存储空间,其中缓存块免于被执行驱逐。

监控内存消耗

数据集所需的内存消耗大小的最佳方法是创建RDD,将其放入缓存中,并查看Web UI中的“存储”页。这个页面会告诉你RDD占用了多少内存。

为了估计特定对象的内存消耗,使用SizeEstimator’s estimate的估计方法,这对于实验不同的数据布局来修整内存使用,以及确定广播变量将占用每个执行器堆的空间。

优化数据结构

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

http://fastutil.di.unimi.it/
设计你的数据结构的选择对象数组和原始类型,而不是标准的java或Scala集合类(如HashMap)。fastUtil库提供的原始类型,与java标准库兼容方便的集合类。

如果可能的话,避免嵌套有许多小对象和指针的结构。

考虑使用数字IDS或枚举对象而不是字符串的键。

如果你有少于32GB的RAM,设置JVM标志-XX:+UseCompressedOops使指针成为四字节而不是八字节。您可以在 spark-env.sh中添加这些选项。

rdd序列化存储

当您的对象仍然过大而无法有效地存储,一个更简单的方法来减少内存使用是存储在序列化的形式,使用序列化存储在RDD持久性API,如MEMORY_ONLY_SER。spark然后将每个RDD分区存储为一个大字节数组。将数据存储在序列化窗体中的唯一缺点是访问时间较慢,因为必须立即对每个对象进行反序列化。我们强烈建议您使用kryo如果你想要连载缓存数据,因为它导致更小的尺寸比java序列化(当然比原java对象)。

GC调优

JVM垃圾收集可能是一个问题,当你有很大的“churn”方面的RDDS存储的程序。(通常是没有问题的程序,只是读了一盘一次然后运行许多操作它。)

当java需要驱逐老对象到新的房间,这就需要通过跟踪你所有的java对象和找到未使用的。

要记住的是,垃圾收集的成本与java对象的数量成正比,所以用更少的对象的数据结构(例如,数组对象而不是LinkedList)大大降低了成本。一个更好的方法是以序列化的形式保存对象,如上所述:现在每个RDD分区只有一个对象(字节数组)。在尝试其他技术之前,如果GC是一个问题,首先要使用的是序列化缓存。

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

衡量GC的影响

GC调整的第一步是收集关于垃圾回收频率和GC时间的统计数据。这可以通过添加-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps到java选项。下一次你的spark的工作运行,每次垃圾收集发生时,您都会看到在worker日志中打印的消息。注意这些日志将在您的群集的工作节点(在工作目录中的stdout 文件)上,而不是在driver上。

GC 高级调优

为了进一步调整垃圾收集,我们首先需要了解JVM中内存管理的一些基本信息:

java堆空间分为青年和老年两地区。年轻一代的目的是保存短命的物体,而旧一代的目的是长寿命的物体。

年轻一代被进一步划分为三个区域[伊登,存活1,存活2 ]。
垃圾收集过程的简化描述:当伊甸满时,在伊甸上运行一个小GC,从伊甸和Surviv1中存活的对象被复制到Surviv2。幸存者区域被交换。如果一个对象足够大或者生存2满了,它就被移动到老年代。最后,当旧的接近满时,调用一个 full GC

在scala中GC调整的目标是确保只有长寿命的RDD存储在旧一代中,并且年轻的一代足够大以存储短命的对象。这将有助于避免完整的GCS收集在任务执行期间创建的临时对象。可能有用的一些步骤是:

1、通过收集GC统计数据来检查是否有太多垃圾收集。如果在任务完成之前多次调用full GC,则意味着没有足够的内存来执行任务。

2、如果有太多的小型收集器,但没有很多主要的GCS,那么为伊甸分配更多内存将有所帮助。您可以将伊甸的大小设置为对每个任务需要多少内存的过度估计。如果伊甸的大小被确定为E,那么你可以使用选项 -Xmn=4/3*E.来设置年轻一代的大小(4/3的扩展是为了解释幸存者区域所使用的空间)。

3、在打印的GC统计表中,如果OldGen接近满,则通过降低spark.memory.fraction来减少用于缓存的内存量;缓存较少的对象比减慢任务执行更好。或者,考虑减少年轻代的规模。这意味着如果你把它设置为上面的话,就降低-Xmn 。如果不是,请尝试更改JVM’s NewRatio parameter参数的值。许多JVM这个默认为2,这意味着旧一代占据了堆的2/3。这个比例应该足够大,使得这个分数超过spark.memory.fraction.

尝试使用 -XX:+UseG1GC.的G1GC垃圾收集器。在垃圾收集是瓶颈的某些情况下,它可以提高性能。
注意,使用大的执行器堆大小,用-XX:G1HeapRegionSize大小增加G1区域大小可能很重要。
例如,如果您的任务是从HDFS读取数据,则可以使用从HDFS读取的数据块的大小来估计任务所使用的内存量。

注意,解压缩块的大小通常是块大小的2倍或3倍。因此,如果我们希望有3个或4个task的工作空间,并且HDFS块大小是128 MB,那么我们可以估计伊甸的大小是4×3×128MB。

监视垃圾收集所占用的频率和时间随新设置的变化。
我们的经验表明GC调整的效果取决于您的应用程序和可用内存的数量。在线上描述了更多的调谐选项,但是在高级别上,管理full GC发生频率有助于减少开销。

http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html

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

并行度
除非为每个操作设置足够高的并行级别,否则群集不会被充分利用。spark根据每一个文件的大小自动设置“map”任务的数量(虽然您可以通过可选参数来控制 SparkContext.textFile等),而对于分布式的“还原”操作,如GypByKy和ReopeByKy,它使用最大的父RDD。分区的数目。您可以将并行级别传递为第二个参数(参见the spark.PairRDDFunctions文档),或者设置config属性 spark.default.parallelism 以更改默认值。一般来说,我们建议在集群中每个CPU核心配置2-3个task。

调整reduce task内存使用
有时,您将获得OutOfMemoryError ,这不是因为RDDS不适合内存,而是因为您的任务之一的工作集(如groupByKey中的一个reduce task)太大。spark的shuffle操作(sortByKey, groupByKey, reduceByKey, join等)在每个任务中构建一个哈希表来执行分组,这通常是很大的。这里最简单的解决方法是增加并行级别,以便每个任务的输入集更小。spark可以有效地支持任务,只要200毫秒,因为它重用一个执行器JVM跨越许多task,它具有低的task启动成本,因此,你可以安全地提高并行级别超过你的集群中的核心数量。

广播大变量

使用SparkContext 中可用的广播功能可以大大减少每个序列化任务的大小,以及在集群上启动作业的成本。如果您的任务使用其中的驱动程序中的任何大对象(例如静态查找表),请考虑将其转换成广播变量。spark打印每个主机上的每个任务的序列化大小,因此您可以查看它以确定任务是否太大;一般任务大于20 KB可能是值得优化的。

数据本地化
数据位置可以对spark作业的性能产生重大影响。如果数据和操作在一起的代码一起,那么计算就快了。但是如果代码和数据分离,则必须移动到另一个。通常,从一个地方到另一个地方传输序列化代码比数据块更快,因为代码大小比数据小得多。spark围绕数据局部化的一般原则构建其调度。

数据位置是数据与代码处理的紧密程度。根据数据的当前位置,有几个级别的局部性。从最近到最远的顺序:

PROCESS_LOCAL
本地数据作为运行代码处于同一JVM中。这是最好的地方。

NODE_LOCAL
数据在同一节点上。示例可能在同一节点上的HDFS中,或者在同一节点上的另一个执行器中。这比进程PROCESS_LOCAL 稍微慢一些,因为数据必须在进程之间运行。

NO_PREF
数据从任何地方同样快速地访问,并且没有本地偏好。

RACK_LOCAL
本地数据位于相同的服务器机架上。数据在同一机架上的不同服务器上,因此需要通过网络发送,通常通过单个交换机。

ANY
任何数据都在网络上的其他地方而不是在同一机架中。

spark倾向于将所有任务安排在最佳位置级别,但这并不总是可能的。在任何空闲执行器上没有未处理数据的情况下,spark切换到较低的局部级别。有两种选择:
a)等待忙碌的CPU释放,开始在同一服务器上的数据上执行任务,
B)在一个需要移动数据的更远的地方立即启动一项新任务。

spark通常是等待一段时间,希望繁忙的CPU释放。一旦超时结束,它就开始把数据从很远的地方移到空闲的CPU上。每个级别之间回退的等待超时可单独或全部在一个参数中配置;详情请参见配置页上的spark.locality参数。如果任务长,并且看到较差的局部性,则应增加这些设置,但默认情况下通常效果良好。

http://spark.apache.org/docs/latest/configuration.html#scheduling

参考资料:

http://spark.apache.org/docs/latest/tuning.html

猜你喜欢

转载自blog.csdn.net/qq_16038125/article/details/80309660
今日推荐