该设计确保了几个理想的性能。首先,不使用缓存的应用程序可以将整个空间用于执行,从而避免不必要的磁盘泄漏。第二,使用缓存的应用程序可以保留最小的存储空间(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 的旧版或”终身”版本中舒适地适应这一堆堆空间。有关详细信息,请参阅下面高级 GC 调优的讨论。
大小数据集所需的内存消耗量的最佳方式是创建 RDD ,将其放入缓存中,并查看 Web UI 中的“存储”页面。该页面将告诉您 RDD 占用多少内存。
为了估计特定对象的内存消耗,使用 SizeEstimator 的 estimate 方法这是用于与不同的数据布局试验修剪内存使用情况,以及确定的空间的广播变量将占据每个执行器堆的量是有用的。
减少内存消耗的第一种方法是避免添加开销的 Java 功能,例如基于指针的数据结构和包装对象。有几种方法可以做到这一点:
将数据结构设计为偏好对象数组和原始类型,而不是标准的 Java 或 Scala 集合类(例如: HashMap )。该 fastutil 库提供方便的集合类基本类型是与 Java 标准库兼容。
尽可能避免使用很多小对象和指针的嵌套结构。
考虑使用数字 ID 或枚举对象而不是键的字符串。
如果您的 RAM 小于32 GB,请设置 JVM 标志 -XX:+UseCompressedOops ,使指针为4个字节而不是8个字节。您可以添加这些选项
当您的对象仍然太大而无法有效存储,尽管这种调整,减少内存使用的一个更简单的方法是以序列化形式存储它们,使用 中的序列化 StorageLevel ,例如: MEMORY_ONLY_SER 。 Spark 将会将每个 RDD 分区存储为一个大字节数组。以序列化形式存储数据的唯一缺点是访问时间较短,因为必须对每个对象进行反序列化。如果您想以序列化形式缓存数据,我们强烈建议 ,因为它导致比 Java 序列化更小的尺寸(而且肯定比原 Java 对象)更小。
当您的程序存储的 RDD 有很大的”流失”时, JVM 垃圾收集可能是一个问题。(程序中通常没有问题,只读一次 RDD ,然后在其上运行许多操作)。 当 Java 需要驱逐旧对象为新的对象腾出空间时,需要跟踪所有 Java 对象并找到未使用的。要记住的要点是,垃圾收集的成本与 Java 对象的数量成正比,因此使用较少对象的数据结构(例如: Ints数组,而不是 LinkedList )大大降低了此成本。 一个更好的方法是如上所述以序列化形式持久化对象:现在每个 RDD 分区只有一个对象(一个字节数组)。 在尝试其他技术之前,如果 GC 是一个问题,首先要使用。
由于任务的工作记忆(运行任务所需的空间)和缓存在节点上的 RDD 之间的干扰, GC 也可能是一个问题。我们将讨论如何控制分配给RDD缓存的空间来减轻这一点。
测量 GC 的影响
GC 调整的第一步是收集关于垃圾收集发生频率和GC花费的时间的统计信息。这可以通过添加 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 到 Java 选项来完成。(有关将 Java 选项传递给 Spark 作业的信息,请参阅)下次运行 Spark 作业时,每当发生垃圾回收时,都会看到在工作日志中打印的消息。请注意,这些日志将在您的群集的工作节点上( stdout 在其工作目录中的文件中),而不是您的驱动程序。
高级 GC 优化
为了进一步调整垃圾收集,我们首先需要了解一些关于 JVM 内存管理的基本信息:
Java堆空间分为两个区域 Young 和 Old 。 Young 一代的目的是持有短命的物体,而 Old 一代的目标是使用寿命更长的物体。
Young 一代进一步分为三个区域[ Eden , Survivor1 , Survivor2 ]。
垃圾收集过程的简化说明:当 Eden 已满时, Eden 上运行了一个小型 GC ,并将 Eden 和 Survivor1 中存在的对象复制到 Survivor2 。幸存者地区被交换。如果一个对象足够老,或者 Survivor2 已满,则会移动到 Old 。最后,当 Old 接近满时,一个完整的 GC 被调用。