1. 内存优化
调优内存使用需要注意一下3点:
- 你的对象占用的内存大小
- 访问你的对象的消耗
- GC的压力
通常,java对象访问是很快的,但是会占用大约2-5倍的空间(相比其内部的原始数据),主要是由于以下几个原因:
- 每个不同的java对象都有一个object header,大约占用16个字节,包含了指向其类的指针,对于像
Int
这样的类型,会有头重脚轻的问题 - java
String
类型大约占了40个字节(因为其中有其他的field,导致其大小超过了原始数据的大小),javaString
是以char[]
来存储的,因为使用UTF-16
编码,每个char
其实占用了2个byte
. 这样的话,一个10-char的String
很容易超过60个bytes - 常见的集合类型,例如:
HashMap
,LinkedList
,使用了链式数据结构,不仅对每个内部对象做了包装,而且该对象不止有object header,还有一个next
指针(大约占8-byte) - 基础类型相对应的集合,通常是以包裹对象的形式存储,例如:
java.lang.Integer
1.1 内存管理概览
Spark程序中的内存使用绝大多数都落入以下2个分类:
- execution: 用于shuffles, joins, sorts and aggregations 这些计算的内存
- storage: 用于缓存,集群间分发数据 的内存
在Spark中,execution和storage 共享一个unified region(M),二者的相互占用如下:
- 当没有使用execution memory的时候,storage可以占用所有可用的内存,反之亦然
- execution可在必要的时候占用一些storage的内存空间,直到storage的空间占比低于一个阈值 R
- 由于实现的复杂性,storage 不会侵占execution 的内存空间
以下2个相关配置,可用用于修改以上提到的 M 和 R:
-
spark.memory.fraction
: M=\text{JVM heap space} * \text{spark.memory.fraction} (默认为0.6), JVM heap space的其余部分主要用于: 用户的数据结构,Spark内部metadata等 -
spark.memory.storageFraction
: R=M * \text{spark.memory.storageFraction} (默认为0.5)
1.2 确定内存开销
-
确定一个dataset占用的内存开销: 将其丢入到cache,并在UI上看
Storage
这一栏的数据 -
确定一个特定对象的内存开销: 使用
SizeEstimator#estimate
方法
1.3 优化数据结构
- 尽量造轮子,使用
array
和基础类型 - 避免嵌套的,带有很多小对象和指针的数据结构
- 尽量使用数字类型或者枚举类型,避免string类型
- 当你的内存小于32GB时,设置JVM标志位:
-XX:+UseCompressedOops
,使用4个byte来表示一个指针
1.4 序列化RDD存储
1.5 GC优化
GC的消耗正比于java对象的个数
1. 衡量GC的影响
通过配置java参数来收集GC的信息: -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
2. 高级GC调优
JVM内存管理简介:
-
Java Heap space 被分成2块: Young 和 Old
-
Young 被进一步分为3块: Eden, Survivor1, Survivor2
GC过程简述:
When Eden is full, a minor GC is run on Eden and objects that are alive from Eden and Survivor1 are copied to Survivor2. The Survivor regions are swapped.
If an object is old enough or Survivor2 is full, it is moved to Old. Finally when Old is close to full, a full GC is invoked
Spark GC调优的目标是: 确保只有长期存在的RDD在老年代,年轻代需要调配到合适的尺寸来存储短期对象,以下是一些checklist:
现象 | 原因/尝试方案 |
---|---|
当一个task完成前,有多次 FULL GC | 没有足够的内存来执行task |
有很多minor GC,较少的 major GC | 分配较大的 Eden 区(例如: Eden估计下来为E,则 -Xmn=4/3*E ,之所以用4/3,是考虑到 survivor 需要的空间 ) |
OldGen 快满了 | 1. 通过降低 spark.memory.fraction 该参数来减少cache消耗的内存 2. 降低 Young的大小 (Old的比例可用大于 spark.memory.fraction ) |
尝试使用 -XX:+UseG1GC ,当executor的内存很大的时候,需要增大: -XX:G1HeapRegionSize
|
|
当从HDFS读数据的时候, Eden 的设置 | Note that the size of a decompressed block is often 2 or 3 times the size of the block. So if we wish to have 3 or 4 tasks' worth of working space, and the HDFS block size is 128 MB, we can estimate size of Eden to be 4*3*128MB
|
2. 其他考虑
2.1 设置并行度
2.2 Reduce Task的内存使用
- task的working set 不够放到内存,例如:
groupByKey
这个算子 - shuffle(
sortByKey
,groupByKey
,reduceByKey
,join
, etc)操作中建立的hash table会占用大量内存
解决方法: 提高并行度
2.3 广播大对象
如果你的task使用了driver端程序中的大对象(例如:静态查询表),可以考虑将其做成一个广播变量.
如果一个task的大小超过了20kB,就可以考虑该优化
2.4 数据本地性
Data Locality 的取值 | 含义 |
---|---|
PROCESS_LOCAL |
数据和运行的代码在同一JVM |
NODE_LOCAL |
数据和运行的代码只在同一个节点,数据需要进行进程间传递 |
NO_PREF |
数据可以在任何地方快速访问,无所谓本地性 |
RACK_LOCAL |
同一机架,数据需要通过转换器 |
ANY |
不在同一机架 |
spark.locality
相关参数的配置
网友评论