spark优化入门

22 篇文章 0 订阅

spark计算的瓶颈可能有很多,比如cpu,带宽,内存等等. 本文主要从序列化以及内存优化两方面来谈.

数据序列化

- 序列化在任何分布式应用程序的性能中起着重要的作用
- 将对象序列化为消耗大量字节的格式会大大减慢计算速度
- spark提供了两个序列化库,java序列化和kryo序列化
- spark默认使用java序列化,但是java序列化比较慢,且序列化后的对象体积大
- kryo序列化比较快,但缺点是并不支持所有的类型.
- kryo使用自定义类需要注册,注册方法如下
val conf = new SparkConf().setMaster(...).setAppName(...)
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
val sc = new SparkContext(conf)

内存调整

  • 主要考虑三方面:
存储对象的内存用量,
访问这些对象的开销,
垃圾回收的开销(如果对象周转率很高的话)

默认情况下,java对象访问很快,但是会占用比原始数据2-5倍的空间.原因如下

**每个不同的 Java 对象都有一个"对象标头"**,该对象大约为 16 字节,包含指向其类的指针等信息。对于数据很少的对象(例如一个Int字段),这可能大于数据。

**Java 在原始字符串数据上具有大约 40 字节的开销**(因为它们将其存储在`StringCharString` 数组中,并保留额外的数据(如长度),并且由于 UTF-16 编码的内部使用,将每个字符存储为两个字节。 因此,10 个字符的字符串可以轻松地消耗 60 字节。

常见的集合类(如 和)使用链接的数据结构,其中每个条目都有一个"包装器"对象(例如 HashMapLinkedListMap.Entry)。此对象不仅具有标头,而且指向列表中的下一个对象的指针(通常每个字节 8 个字节)。

**基元类型的集合通常将它们存储为"装箱"对象**,如 。`java.lang.Integer`

为了高效使用内存,请看下面

内存管理概述

  • spark内存分两部分,执行内存和存储内存
  • 执行内存是之执行shuffle,join,sort 和aggregation等计算时的内存
  • 存储内存是指用于缓存和跨集群传播数据的内存
  • 这两块内存是共享的,就是可以互相占用
  • 如有必要,执行内存可以驱逐共享内存
  • 该设计可以提高内存的使用效率

有两个参数可以调整这个,但是普通的用户一般都不需要调整.

spark.memory.fraction表示 的大小为 (JVM 堆空间 - 300MiB) 的大小(默认 0.6)。其余空间 (40%)为用户数据结构、Spark 中的内部元数据以及在稀疏和异常大型记录的情况下防止 OOM 错误保留。
spark.memory.storageFraction表示 的大小为 分数(默认 0.5)。 是缓存块不受执行驱逐的存储空间。

确定内存消耗

调整数据集所需的内存消耗量的最佳方法是创建 RDD,将它放入缓存中,并查看 Web UI 中的"存储"页。该页将告诉您 RDD 占用的内存量。

若要估计特定对象的内存消耗,请使用 SizeEstimatorestimate’的方法。这可用于试验不同的数据布局以修剪内存使用情况,以及确定广播变量在每个执行器堆上将占用的空间量。

调整数据结构

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

将数据结构设计为首选对象数组和基元类型,而不是标准 Java 或 Scala 集合类(例如HashMap )。快速利用库为与 Java 标准库兼容的基元类型提供了方便的集合类。

尽可能避免使用大量小对象和指针的嵌套结构。

请考虑使用数字 ID 或枚举对象,而不是字符串作为key

如果 RAM 小于32 GIB,请将 JVM 标志设置为指针为 4 个字节而不是 8 个字节。您可以在 "spark-env.sh 中添加这些选项。-XX:+UseCompressedOops

序列化 RDD 存储

  • 如果对象仍然太大,无法有效存储,就最好以序列化的形式存储MEMORY_ONLY_SER
  • 但是也有缺点,就是访问速度变慢,因为要反序列化
  • 最好使用Kryo序列化,因为大小比java小的多

GC优化

  • 垃圾回收的成本和对象的数量成正比
  • 如果GC有问题,首选的解决方法是以序列化的形式存储对象

测量 GC 的影响

GC 调优的第一步是收集有关垃圾回收发生的频率和花费 GC 时间量的统计信息。这可以通过添加到Java选项来完成。

高级 GC 调优

为了进一步调整垃圾回收,我们首先需要了解一些有关 JVM 内存管理的基础知识:

Java 堆空间分为两个区域"年轻"和"旧"。年轻一代意在持有短期对象,而老一代则用于具有较长寿命的对象。

年轻一代被进一步分为三个区域[伊登,幸存者1,幸存者2]。

垃圾回收过程简体说明:当 Eden 已满时,在 Eden 上运行一个次要 GC,并且从 Eden 和幸存者 1 中活动的对象将复制到幸存者 2。幸存者区域被交换。如果对象已足够旧或幸存者 2 已满,则将移动到"旧"。最后,当 Old 接近满时,将调用一个完整的 GC。

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

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

如果有太多的次要集合,但主要的 GCs 不是很多,为 Eden 分配更多的内存会有所帮助。您可以将 Eden 的大小设置为高估每个任务所需的内存量。如果伊甸园的大小被确定为 ,那么你可以设置年轻一代的大小使用的选项。(增加 4/3 也考虑到幸存者区域使用的空间。E-Xmn=4/3*E

在打印的 GC 统计信息中,如果 OldGen 接近已满,则通过降低 ;最好缓存较少的对象,而不是减慢任务执行速度。或者,考虑减少年轻一代的规模。这意味着如果已设置为上述,将降低。如果没有,请尝试更改 JVM 参数的值。许多 JVM 默认为 2,这意味着老一代占用了堆的 2/3。它应该足够大,以使这个分数超过。spark.memory.fraction-XmnNewRatiospark.memory.fraction

使用 尝试 G1GC 垃圾回收器。在某些情况下,垃圾回收是瓶颈,它可以提高性能。请注意,对于大型执行器堆大小,增加G1 区域大小可能非常重要。-XX:+UseG1GC-XX:G1HeapRegionSize

例如,如果任务正在从 HDFS 读取数据,则可以使用从 HDFS 读取的数据块的大小来估计任务使用的内存量。请注意,解压缩块的大小通常是块大小的 2 或 3 倍。因此,如果我们希望有 3 或 4 个任务的工作空间,并且 HDFS 块大小为 128 MiB,我们可以估计伊甸园的大小为 。43128MiB

监视垃圾回收的频率和时间如何随着新设置而变化。

我们的经验表明,GC 调优的效果取决于您的应用程序和可用内存量。在线描述的调优选项更多,但在高级别上,管理完整 GC 发生的频率有助于减少开销。

可以通过设置或在作业配置中指定执行器的 GC 调优标志。spark.executor.defaultJavaOptionsspark.executor.extraJavaOptions

其他注意事项

并行度

  • spark会依据文件大小自动设置并行度,当然也可以自定义
  • 对于reduce操作,会继承父RDD的最大分区数
  • 建议每个core可以执行2-3个任务

读取文件时的并行度
如果您的工作使用 Hadoop 输入格式在 RDD 上工作(例如,通过 ),则并行性通过spark.hadoop.mapreduce.input.fileinputformat.list-status.num-threads(当前默认值为 1)进行控制。

对于具有基于文件的数据源的 Spark SQL,您可以调整和改进列表并行性。有关详细信息,请参阅 Spark SQL 性能调优指南。spark.sql.sources.parallelPartitionDiscovery.thresholdspark.sql.sources.parallelPartitionDiscovery.parallelism

reduce任务的内存使用情况

  • 如果某个task过大,可能会内存溢出
  • spark的shuffle操作会创建一个hash table执行聚合,就可能会比较大(groupByKey sortByKey groupByKey reduceByKey join)
  • 解决这个问题的办法是提高并行度,这样每个task处理的数据量就比较小了
  • 因为spark是线程机制,启动一个任务消耗比较小,所以不必担心像mr那样启动比较慢

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

数据本地化

  • 如果数据和操作它的代码在一块,就会比较快
  • 如果数据和操作它的代码是分开的,一个必须移动到另一个,一般移动代码

数据本地化有几个级别:

PROCESS_LOCAL数据与正在运行的代码位于相同的 JVM 中。这是最好的地方
NODE_LOCAL数据位于同一节点上。示例可能在同一节点上的 HDFS 中,或在同一节点上的另一个执行器中。这比数据必须在进程之间传输要慢一些PROCESS_LOCAL
NO_PREF数据从任何地方访问同样快速,并且没有本地化选项
RACK_LOCAL数据位于同一服务器机架上。数据位于同一机架上的不同服务器上,因此需要通过网络(通常通过单个交换机)发送
ANY数据位于网络的其他位置,而不是在同一机架中

Spark 倾向于在最佳本地化级别安排所有任务,但这并不总是可能的。

当有cpu空闲时,spark会将数据从很远的地方移动到空闲的 CPU。

总结

这是一个简短的指南,指出在调整 Spark 应用程序时您应该知道的主要问题 - 最重要的是,数据序列化和内存调优。对于大多数程序,切换到 Kryo 序列化和以序列化形式保留数据将解决最常见的性能问题。

官网原文参考
https://spark.apache.org/docs/latest/tuning.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值