Tuning Spark( 内存管理和数据序列化)

本文探讨了Spark程序的内存管理和数据序列化优化,包括使用Kryo提高序列化效率,理解Spark内存架构,调整数据结构以减少内存消耗,以及如何通过存储级别进行RDD序列化。同时,文章还提到了垃圾收集的调整方法,如通过调整JVM参数监控和优化GC性能。
摘要由CSDN通过智能技术生成

由于大多数spark计算的内存特性,spark程序可能会受到集群中任何资源的瓶颈:CPU、网络带宽或内存。大多数情况下,如果数据适合内存,瓶颈是网络带宽,但有时还需要进行一些调整,例如以序列化形式存储RDD,以减少内存使用。

数据序列化

序列化在任何分布式应用程序的性能中起着重要的作用。 很慢的将对象序列化或消费大量字节的格式将会大大减慢计算速度。 这可能是优化 Spark 应用程序的第一件事。 Spark 宗旨在于方便和性能之间取得一个平衡(允许使用操作中的任何 Java 类型)。 提供了两种序列化库:

  • Java serialization: 默认情况下,使用 Java ObjectOutputStream 框架的 Spark 序列化对象,并 Java 序列化是灵活的,但通常相当缓慢,并导致许多类的大型序列化格式。
  • Kryo serialization: Spark 也可以使用 Kryo 库(版本2)来更快地对对象进行序列化。

可以使用 SparkConf 初始化作业 并进行调用来切换到使用 Kryo

conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")。

Spark 自动包含 Kryo 序列化器,用于 Twitter chill 中 AllScalaRegistrar 涵盖的许多常用的核心 Scala 类。

要使用 Kryo 注册自己的自定义类,请使用该 registerKryoClasses 方法。

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

内存调优

内存管理概述

Spark 中的内存使用大部分属于两类:执行和存储。执行存储器是指用于以随机移动,连接,排序和聚合计算的存储器,而存储内存是指用于在集群中缓存和传播内部数据的内存。在 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 缓存块中的缓存被执行驱逐的存储空间。
    spark.memory.fraction 应该设置值,以便在 JVM 的旧版或”终身”版本中舒适地适应这一堆堆空间。
确定内存消耗

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

调整数据结构

减少内存消耗的第一种方法是避免添加开销的 Java 功能,例如基于指针的数据结构和包装对象。

  • 将数据结构设计为偏好对象数组和原始类型,而不是标准的 Java 或 Scala 集合类(例如: HashMap )。该 fastutil 库提供方便的集合类基本类型是与 Java 标准库兼容。
  • 尽可能避免使用很多小对象和指针的嵌套结构。
  • 考虑使用数字 ID 或枚举对象而不是键的字符串。
  • 如果您的 RAM 小于32 GB,请设置 JVM 标志 -XX:+UseCompressedOops ,使指针为4个字节而不是8个字节。可以添加这些选项 spark-env.sh
序列化 RDD 存储

当您的对象仍然太大而无法有效存储,尽管这种调整,减少内存使用的一个更简单的方法是以序列化形式存储它们,使用 RDD 持久性 API 中的序列化 StorageLevel 。

垃圾收集调整

当程序存储的 RDD 有很大的”流失”时, JVM 垃圾收集可能是一个问题。(程序中通常没有问题,只读一次 RDD ,然后在其上运行许多操作)。

测量 GC 的影响

GC 调整的第一步是收集关于垃圾收集发生频率和GC花费的时间的统计信息。这可以通过添加 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 到 Java 选项来完成。

高级 GC 优化

Spark 中 GC 调优的目的是确保只有长寿命的 RDD 存储在 Old 版本中,并且 Young 版本的大小足够存储短命期的对象。这将有助于避免使用完整的 GC 来收集任务执行期间创建的临时对象。可能有用的一些步骤是:

  • 通过收集 GC 统计信息来检查垃圾收集是否太多。如果在任务完成之前多次调用完整的 GC ,这意味着没有足够的可用于执行任务的内存。

  • 如果太小的集合太多,而不是很多主要的 GC ,为 Eden 分配更多的内存将会有所帮助。可以将 Eden 的大小设置为对每个任务需要多少内存的估计。如果确定 Eden 的大小 E ,那么可以使用该选项设置年轻一代的大小 -Xmn=4/3*E 。(按比例增加4/3是考虑幸存者地区使用的空间。)

  • 在打印的 GC 统计信息中,如果 OldGen 接近于满,则通过降低减少用于缓存的内存量 spark.memory.fraction ; 缓存较少的对象比减慢任务执行更好。或者,考虑减少年轻一代的大小。这意味着 -Xmn 如果您将其设置为如上所述降低。如果没有,请尝试更改 JVM NewRatio 参数的值。许多 JVM 默认为2,这意味着 Old 版本占据堆栈的2/3。它应该足够大,使得该分数超过 spark.memory.fraction。

  • 尝试使用 G1GC 垃圾回收器 -XX:+UseG1GC。在垃圾收集是瓶颈的一些情况下,它可以提高性能. 请注意,对于大型 excutor 的堆大小,通过设置 -XX:G1HeapRegionSize 参数来增加 G1 区域的大小 是非常重要的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值