Spark调优

调优主要面临的问题是网络和内存。

1 序列化

(1) 两种序列化库

  • Java

    默认方式。

    支持所有实现了java.io.Serializable接口的类。

    通常慢且压缩效率相对低。

  • Kryo

    快且效率较高。

    需要注册相应的类,不完全支持java.io.Serializable。

    Spark内部用于基本类型、基本类型数组和字符串序列化。

    推荐在带宽紧张的场景使用。

切换序列化库:调用 conf.set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”),将用于工作节点交换和序列化到磁盘中。

注册类:

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

详见Kryo documentation

注意:

  • 对象过大时,需要调整序列化上限 spark.kryoserializer.buffer
  • 没有注册类也能正常工作,但是需要存储每个对象的完全类名称,浪费空间。

2 内存调优

(1) 内存调优考量

  • 对象存储消耗
  • 对象访问消耗
  • 垃圾回收周转

(2) Java对象存储

通常,Java访问较快,但空间占用可达有效数据的数倍。原因如下:

  • 对象头

    约16字节

  • 指针和编码

    如字符串类使用约40字节额外数据组织字符,以及采用Unicode编码。通常10个字符长度占用约60字节数据。

  • 集合与包装类

(3) 内存管理概览

Spark内存占用主要分为执行内存和存储内存。

执行内存用于数据交换、连接和聚合过程中的计算。存储内存用于缓存、内部数据分发。

执行内存和存储内存共享一个统一的内存区域M,两者可以根据需要分配。当内存使用量小于界限R时,执行内存不能驱离存储内存。

Spark提供了调配内存比例的方法.

  • spark.memory.fraction指定总内存M占虚拟机内存的比例,默认0.6,其余部分可用于保存用户数据、元数据和浮动等
  • spark.memory.storageFraction界限R占M的比例,默认0.5.

(4) 内存消耗计算

有以下两种方法:

  • 缓存RDD,在Web UI中查看占用
  • 使用SizeEstimator.estimate()估计

(5) 数据结构调优

通过以下方式避免增加负载的Java特性:

  • 使用基本类型和数组,而不是集合。可以使用fastutil选择替代方案
  • 尽量减少使用嵌套大量对象和指针的数据结构
  • 键值尽量使用数值或枚举类
  • 内存小于32GB时,使用-XX:+UseCompressedOops将指针类型从8字节转为4字节,可以在spark-env.sh中配置

(6) 序列化RDD存储

使用序列化减少内存使用,代价是减慢访问速率。

推荐使用Kryo,压缩比更高

(7) 垃圾回收调优

垃圾回收需要跟踪对象引用,对象数量影响该部分工作。为了减少对象数量,建议使用数组,而不是集合。

针对垃圾回收问题,第一步是使用序列化,减少内存使用。

1) 衡量影响

使用JVM参数-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

注意日志输出到各工作节点stout文件中

2) 高级GC调优

目的是保证只有长期活跃RDD保存在老年代,新生代有足够的空间保存短期对象。避免full GC清理任务执行时的临时对象。

  • 检查GC频率

    任务完成前多次full GC意味着内存不足

  • minor GC多而full GC不多

    给新生代分配更多的内存,如乘以系数4/3

  • 老年代快满时,减少其他区域内存

    减少缓存对象的内存。

    或者减少新生代内存,通过-Xmn或NewRatio(老年代对新生代的倍数)参数

  • 在垃圾回收成为瓶颈的场景中,使用G1垃圾回收

    对于较大的执行器堆内存,需要使用-XX:G1HeapRegionSize增加G1 region size

  • HDFS数据占用估计

    数据块(128MB)*解压缩(通常增至2-3倍)*任务数

  • 监控垃圾回收耗时和频率

注意:

调优可通过spark.executor.extraJavaOptions为每个作业配置

调优效果取决于应用程序和可用内存,更多方式详见 many more tuning options

3 其他

(1) 并发数

设置足够高的并发数,以充分利用集群计算资源。

建议并发数设置为可用CPU核心数的2-3倍

(2) Reduce任务内存使用

由于聚合阶段为每个任务使用映射表,可能导致内存溢出。

建议通过增加并发数,来减少任务处理的数据量。

(3) 广播大变量

广播变量可以减少任务序列化数据量和降低集群作业消耗。

建议广播容量大于20KB的数据。

(4) 数据本地化

数据和代码通常不再同一进程中。由于数据量的悬殊,通常将代码转移到靠近数据的节点执行。

Spark数据本地化等级如下,从上到下相距越远,数据传输速率越低:

  • PROCESS_LOCAL 同一JVN进程中
  • NODE_LOCAL 同一节点,数据通常需要在进程间传递
  • NO_PREF 没有区分
  • RACK_LOCAL 同一机架,需要通过交换机网络传输
  • ANY 不同机架,需要网络传输

任务执行时,Spark有以下两种选择:

  • 等待所需数据节点可用(默认)
  • 传输数据到空闲节点计算

可通过spark.locality参数调整

4 总结

更多调优方式详见Spark mailing list

参考资料

Tuning Spark