调优主要面临的问题是网络和内存。
1 序列化
(1) 两种序列化库
Java
默认方式。
支持所有实现了java.io.Serializable接口的类。
通常慢且压缩效率相对低。
Kryo
快且效率较高。
需要注册相应的类,不完全支持java.io.Serializable。
Spark内部用于基本类型、基本类型数组和字符串序列化。
推荐在带宽紧张的场景使用。
切换序列化库:调用 conf.set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”),将用于工作节点交换和序列化到磁盘中。
注册类:
1 | val conf = new SparkConf().setMaster(...).setAppName(...) |
注意:
- 对象过大时,需要调整序列化上限 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