- 信息收集
Spark包含一个基于成本的查询优化器,它在使用结构化API时根据输入数据的属性
来计划查询。但是,基于成本的优化器需要收集(并维护)关于数据表的统计信息。
这些统计信息包含两类:数据表级的和列级的统计信息。统计信息收集仅适用于命名
表,不适用于任意的DataFrame或RDD。
要收集表级统计信息,可以运行以下命令:
ANALYZE TABLE table_name COMPUTE STATISTICS
要收集列级统计信息,可以命名特定列:
ANALYZE TABLE table_name COMPUTE STATISTICS FOR
COLUMNS column_name1,column_name2, …
列级别的统计信息收集速度较慢,但这些信息为基于成本的列级优化器提供了如何使
用这些列的更多信息。这两种统计信息可以帮助优化连接操作、聚合操作、过滤操
作,以及其他一些潜在的操作等(例如,自动选择何时进行广播连接)。
- GC优化
垃圾回收调优的第一步是统计垃圾回收发生的频率和时间。你可以使用s p a r k.
executor.extraJavaOptions配置参数添加-verbose:gc –XX:+ PrintGCDetails –XX:+
PrintGCTimeStamps到Spark的JVM选项来完成此操作。下次运行Spark作业时,每次
发生垃圾回收时都会在工作节点的日志中打印相关信息,这些日志位于集群工作节点
上(在其工作目录中的stdout文件中),而不在驱动器中。
垃圾收集调优
为了进一步对垃圾回收调优,你首先需要了解JVM中有关内存管理的一些基本信息:
• Java堆空间分为两个区域:新生代(Young)和老年代(Old)。新生代用于保
存短寿命的对象,而老年代用于保存寿命较长的对象。
• 新生代被进一步划分为三个区域:Eden,Survivor1,以及Survivor2。
以下是对垃圾回收过程的简单描述:
• 当Eden区域满了时,在Eden上运行一个小型垃圾回收系统,Eden和Survivor1区
域中活跃的对象被复制到Survivor2区域中。
• Survivor1区域与Survivor2区域交换。
• 如果一个对象足够旧,或者如果Survivor2区域已满,则该对象将移至老年代
(Old)区域。
• 最后,当老年代区域接近装满时,将调用完整的垃圾回收。这涉及遍历堆中的所
有对象,删除未引用的对象,以及移动其他对象以填充未使用的空间,所以它通
常是最慢的垃圾回收操作。Spark中垃圾回收调优的目标是确保只有长寿命的数据对象存储在老年代(Old)区域
中,并且新生代(Young)区域的大小足以存储所有短期对象。这将有助于避免完整
的垃圾回收处理在任务执行过程中创建的临时对象。以下是可能有用的一些步骤。
收集垃圾回收统计信息以确定它是否过于频繁运行。如果在任务完成之前多次调用完
整的垃圾回收,则意味着没有足够的内存可用于执行任务,因此应该减少Spark用于
缓存的内存大小(spark.Memory.fraction)。
如果有很多小型的垃圾回收但大型垃圾回收很少时,为Eden区域分配更多内存空间将
会有所帮助。你可以将Eden区域的大小设置为略大于每个任务可能需要的内存总量。
如果Eden区域的大小确定为E,则可以使用选项-Xmn=4/3×E来设置Young区域的大小
(比例系数4/3也是为了给Survivor区域腾出空间)。
例如,如果你的任务正在从HDFS中读取数据,则可以使用从HDFS中读取的数据块的
大小来估计该任务使用的内存量。请注意,解压缩之后块的大小通常是压缩块大小的
两倍或三倍。所以如果你想要三到四个任务的工作空间,而HDFS块大小为128 MB,
我们可以估计Eden区域的大小为43128MB。
试着通过–X X:+U s e G1G C选项来使用GIGG垃圾回收器。如果垃圾回收成为瓶颈,而且
你无法通过调整Young和Old区域大小的方法来降低它的开销的话,那么使用GIGG可
能会提高性能。注意,对于较大执行器的堆内存大小,使用-XX:G1HeapRegionSize增
加G1区域大小非常重要。
监测垃圾回收的频率和所花费的时间如何随新设置的变化而发生变化。
我们的经验是,垃圾回收调优的效果取决于你的应用程序和可用内存量。关于垃圾
回收调优的方法还有很多,你可以从网上找到,但在较高的层次上,设置完整垃
圾回收的频率可以帮助减少开销。你可以通过在作业配置中设置s p a r k.e x e c u t o r.
extraJavaOptions来指定执行器的垃圾回收选项。
- 尽量不适用UDF
一般来说,避免使用UDF是一个很好的优化策略。UDF是昂贵的,因为它们强制将
数据表示为JVM中的对象,有时在查询中要对一个记录执行多次此操作。你应该尽
可能多地使用结构化API来执行操作,因为它们将以比高级语言更高效的方式执行
转换操作