Spark调优(0.8.1中文版)

翻译自Spark官方文档 Tuning Spark,由团队的月禾mm初审,以及微博上的Spark达人@crazyjvm复审,非常感谢

Spark调优

由于Spark大部分计算在内存中进行的特性,Spark程序会受到集群中以下任一因素制约:CPU,网络带宽,或内存。最常见的情况,如果数据和内存大小匹配的话,那么瓶颈就是网络带宽。但是有时候,你还是需要做一些调优,例如storing RDDs in serialized form来减少内存的使用。这个指引将覆盖2个大主题:数据序列化,这对好的网络性能和减少内存使用起关键性作用,以及内存调优。另外我们还会涉及几个小话题。

数据序列化

序列化在任何分布式应用中都扮演着一个重要的角色。那些序列化很慢,或占用大量字节的格式,都将会大大拖慢计算速度。通常,在优化Spark应用时这会是你需要调优的第一件事情。Spark致力于在方便和性能间取得平衡(允许你在操作中使用任何的Java类型),它提供了2个序列化库:

  • Java序列化(Java serialization):默认情况下,Spark使用Java的ObjectOutputStream框架来序列化对象,可以兼容所有实现了java.io.Serializable接口的对象。通过扩展java.io.Externalizable接口,你可以更加细致的控制序列化性能。Java的序列化很灵活,但是通常很慢,大量的类会导致大量的序列化格式。
  • Kryo序列化(Kryo serialization): Spark也可以使用Kyro库(版本2)来更快的序列化对象。Kryo比Java序列化更快,更紧凑(通常是Java序列化的10倍),但是不支持所有的Serializable类型,而且需要你把程序中要用到的类预先注册,从而获得更好的性能。

你可以通过在创建SparkContext之前调用System.setProperty(“spark.serializer”, “spark.KryoSerializer”)切换到使用Kryo。我们不使用它作为默认实现的唯一理由,是它需要用户自己注册一下。但是我们推荐使用它,在任何对网络要求较高的应用。

最后,要在Kryo中注册你的类,你需要要建立一个公共类来继承org.apache.spark.serializer.KryoRegistrator类,并设置spark.kryo.registrator的系统属性指向它,如下:

Kryo的文档,描述了更多的高级注册选项,例如添加自定义的序列化代码。

如果你的对象很大,你需要增强Spark.kryoserializer.buffer.mb的系统属性,该值默认是2,但是它需要足够大来容纳你序列化中最大的对象。

最后,如果你不注册你的类,Kryo依然可以工作,但是它会对每个对象都存储全类名,这将是非常浪费的。

内存调优

内存使用调优有3个考虑点:你的对象使用的内存量(你很可能希望将你所有的数据,装入内存);访问对象的代价;集合垃圾回收的开销(如果从对象角度来说你有较高的进出)

默认来说,Java对象访问起来很快,但是会很容易就消耗比成员变量的原始数据大2-5倍的空间。这是由于如下几个原因:

  • 每一个Java对象都有一个”对象头部“,大概是16个字节,包含了诸如指向它的类的指针的信息。对于一个包含非常小的数据的对象(例如Int成员变量),这个可能比数据还要大。
  • Java字符串比起原始的字符串数据有40个字节的额外开销(因为它们将它存储在一个字符数组中,并保存额外的数据,例如长度),而且由于Unicode的原因,每个字符要占用2个字节。一个10字符的字符串,会轻松的消耗60个字节。
  • 通用集合的类,例如HashMapLinkedList, 使用链式数据结构,因此,每一个Entry都会有一个封装对象(例如:Map.Entry)。这个对象不仅仅有头部,而且还会有指针(典型情况下每个8个字节)指向列表中的下一个对象。
  • 原始类型的集合通常将他们存储为封箱对象进行自动装箱。例如java.lang.Integer

本章节将会讨论如何判断你的对象的内存使用量,以及如何改进它,无论是通过改变你的数据结构,或者是通过用序列化的方式来存储数据。我们将会谈到调优Spark的缓存大小,还有Java垃圾回收器。

判断内存消耗

判断内存消耗大小的最佳方法,就是创建一个RDD,把它放进缓存,然后看SparkContext上的Driver程序的日志。日志会告诉你,每个分区消耗了多少的内存,把这些内存加起来,你就能够得到RDD的总大小。你会看到类似主要的消息:

这个代表了RDD 0 的分区 1,消耗了717.5KB的内存。

调优数据结构

减少内存消耗的第一方法,是避免使用增加额外负担的Java特性,例如基于指针的数据结构和封装对象。这有几个方法来实现它:

  1. 设计你的数据结构使其倾向于使用对象数组和原始类型,而不是标准的Java或Scala集合类(例如HashMap)。fastutil库提供了方便的原始类型集合类,兼容Java的标准库。
  2. 尽可能避免带着众多的小数据和指针的嵌套的数据结构。
  3. 考虑使用数字型ID或枚举型对象来代替字符串作为Keys。
  4. 如果你的内存小于32GB,设置JVM的标志为-XX:+UseCompressedOops使得让指针为4个字节,而不是8个。而且,在Java 7或者更高的版本上,尝试使用-XX:+UseCompressedStrings以8位每个字符来存储ASCII字符串。你可以在spark-env.sh中添加这些配置项.

序列化RDD的存储

当你的对象太大,以至于不管如何调优,都不能被高效的存储。一个更简单减少内存使用的方式是以序列化的方式存储它们,使用在RDD persistence API中的序列化存储级别,例如MEMORYONLYSER. Spark将会把每个RDD分区作为一个大的字节数组来存储。将数据用序列化的方式存储的唯一缺点是较慢的存取时间,由于需要在运行中反序列化每一个对象。如果你想要以序列化的形式缓存数据,我们强烈的推荐使用Kryo,因为它将比Java序列化小很多。(当然比原始的Java对象更小)

垃圾回收调优

当你有大量作为RDDs的”churn“存储在你程序中时,JVM垃圾回收可能成为一个问题。(如果是对于那些读取一个RDD一次,然后运行很多的操作符的程序,这通常不会成为问题)当Java需要抛弃大量的旧对象来腾出新空间给新对象时,它需要跟踪所有的JAVA对象,来找出无用的那些。要记住的重点是垃圾回收的成本与Java对象的数目成正比,因此选择有更少对象的数据结构(比如数组用Ints而不是LinkedList)将会极大的降低开销。一个更好的办法是将对象以序列化的形式进行保存,正如上面所描述的:现在每一个RDD分块将会只有一个对象(一字节数组)。如果GC存在问题,在尝试其他技术之前,可以先尝试一下序列化缓存(serialized caching)。

由于你的工作内存和缓存在结点上的RDD的相互干扰,GC可能会成为一个问题。我们将讨论如何控制RDD缓存空间分配来缓解这个问题。

衡量GC的影响

垃圾回收调优的第一步,是收集关于垃圾回收发生的的频率和GC耗费时间的统计数据。这可以通过添加-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamp到SPARK_JAVA_OPTS环境变量来完成。你下一次运行Spark Job时,会看到每一次垃圾回收时,消息在Worker的日志中打印出来。注意这些日志会在你的集群Worker节点上(它们工作目录的stdout文件),而不是在你的Driver程序上。

缓存大小调优

GC一个重要的配置参数,是缓存RDDs需要用的内存量。默认来说,Spark使用66%已配置的Executor内存(spark.executor.memory或者SPARK_MEM)来缓存RDDs。这意味这33%的内存可以用来容纳任务执行中创建的任意对象。

当你的任务变慢,而且你的JVM正在频繁的垃圾回收或者内存溢出时,降低这个值可以帮助减少内存的消耗。假设把它改到50%,你可以调用System.setProperty(“spark.storage.memoryFraction”, “0.5”).结合使用序列化缓存,使用一个更小的缓存,足以缓解大部分的垃圾回收问题。如果你对更进一步的JAVA垃圾回收感兴趣,那么请继续阅读下文。

高级垃圾回收调优

为了更好的垃圾回收调优,我们首先必须明白一些关于JVM内存管理的基本信息:

  • Java的堆空间被切分成2个区:老年代和新生代。新生代用来保存生命周期短的对象,而老年代用于保存有长生命周期的对象。
  • 新生代被进一步切分成三个区[Eden, Survivor1, Survivor2]
  • 关于垃圾回收步骤的简单描述是:当Eden区满了,一个Minor GC会在Eden区触发,存活在Eden和Survivior1区的对象会被拷贝到Survior2区。Survivior区域被交换。当对象够老,或者Survivior2满了,它会被移动到Old区。最后,当Old区满了,一次full GC就会被触发。

Spark垃圾回收调优的目的,在于确保只有长生命周期的RDD被存储在年长代,而年轻代有足够的空间来存储短生命周期的对象。这可以帮助避免过多的Full GC在任务执行的期间收集临时对象。可能有用的步骤如下:

  • 通过收集GC统计检查是否有太多的垃圾回收。如果Full GC在一个Task完成前被触发多次,这意味着没有足够的内存来执行任务。
  • 在被印出的GC统计中,如果老年代接近满了,要减少用于缓存的内存量。这可以通过设置spark.storage.memoryFraction属性来实现。缓存少一些的对象比任务执行慢要相对好一些。
  • 如果有很多的Minor GC,而不是很多的Full GC,分配更多的内存给Eden代会有所帮助。你可以把Eden设置的比每个任务估计会需要的内存稍微大些。 如果Eden的大小被估计为E,那么你可以通过选项-Xmn=4/3E*来设置年轻代的大小。(4/3的比例也是为了留出被survivor regions使用的空间)
  • 例如,如果你的任务,是从HDFS读取数据,那么任务使用的内存量,可以通过数据块的大小来估计。注意一个解压后数据块的大小通常是这个块大小的2到3倍。所以如果我们希望希望有3到4个任务大小的工作空间,而且HDFS的Block Size是64MB,我们可以估计Eden的大小为4*3*64MB.
  • 监控垃圾回收的频率和时间随着设置的变化。

我们的经验建议,垃圾回收调优的效果,取决于你的应用和可用的内存。网上有更多的在线调优的选择,但是在一个高的层次上,控制Full GC发生的频率,可以有效的帮助降低预算。

其他需要考虑的内容

并行度

除非你为每一步操作都设置足够高的并行度,否则集群不会得到最大化的利用。对于”map”操作,Spark会根据文件的大小自动设置在每个文件上的任务数(尽管你可以通过SparkContext.textFile的可选参数,来控制它);对于分布式的”reduce”操作,例如groupByKeyreduceByKey,它会使用最大一个父RDD分区数目作为分块数。你可以传递并行度作为第二个参数(参考spark.PairRDDFunctions文档),或者设置系统属性: spark.default.parallelism来改变默认值。总的来说,在集群中,我们推荐在每个CPU Core上分配2-3个Tasks。

Reduce任务的内存使用

有时你会遇到OutOfMemoryError,不是因为RDDs在内存中装不下了,而是因为你的tasks中有一个工作集,例如在groupByKey中一个Reduce任务太大了。Spark的Shuffle操作(sortByKey,groupByKey,reduceByKey,join,等)会,在每一个任务中建立一个哈希表来进行grouping操作,这个通常会很大。修复这个问题的最简单方式,是增加并行度,使得每个任务的输入集能够更小。因为Spark能够在一个Worker的Jvm中的所有的Task中有效的重用内存,并有一个很低的任务启动开销,它能够支持执行时间短到200ms的任务。所以你可以安全的增加并行度,即使多于你集群中的核数。

广播大变量

使用SparkContext中的广播功能(broadcast functionality),能够很大的降低每个任务的大小,以及在集群上启动一个任务的开销。如果你的任务,调用Driver程序中的任意大对象(例如一个静态查询表),那可以考虑将它转变为一个广播变量。Spark在Master上会打印每个Task序列化后的大小。所以你可以通过查看它,来判断你的Task到底是不是太大了。一般来说,超过20KB大小的Task可能就需要该优化了。

总结

这是一个简单的指引,指出在你优化一个Spark应用时,需要了解的最关键点。最重要的就是数据序列化和内存调优。对于大部分的程序来说,切换到Kryo序列化和用序列化的方式持久化数据,可以解决大部分常见的性能问题。如果有任何关于调优最佳实践的问题,欢迎到Spark的邮件列表中进行询问。

Posted in 数据, 机器学习. Tagged with spark, 调优.

原博客链接http://rdc.taobao.org/?p=2034

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、下载 4使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、 4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.m或d论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 、1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。、资源 5来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。、资 5源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值