1 为什么需要持久化?
在Spark中,RDD采用惰性求值的机制,每次遇到行动操作,都会从头开始执行计算。如果整个Spark程序中只有一次行动操作,这当然不会有什么问题。但是在一些情形下,我们需要多次调用不同的行动操作,这就意味着,每次调用行动操作,都会触发一次从头开始的计算。这对于迭代计算而言,代价是很大的,迭代计算经常需要多次重复使用同一组数据,可以通过持久化(缓存)机制避免这种重复计算的开销。另外,当计算过程中出现错误需要重新计算时,spark会向父级RDD寻找数据,这时候也需要父级RDD进行持久化
三个缓存算子都可以进行持久化:cache()、persist()和checkPoint()
2 cache和persist
cache():源码中,cache()是调用了persist()
persist():源码中,persist()是调用了persist(StorageLevel.MEMORY_ONLY),默认持久化到内存中。
persist还可以指定不同的存储策略
可以结合一下源码:
object StorageLevel {
val NONE = new StorageLevel(false, false, false, false)
val DISK_ONLY = new StorageLevel(true, false, false, false)
val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
val MEMORY_ONLY = new StorageLevel(false, true, false, true)
val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
val OFF_HEAP = new StorageLevel(true, true, true, false, 1)
...
}
class StorageLevel private(
private var _useDisk: Boolean,
private var _useMemory: Boolean,
private var _useOffHeap: Boolean,
private var _deserialized: Boolean,
private var _replication: Int = 1)
构造函数中几个参数的意思:
- _useDisk:是否使用磁盘
- _useMemory:是否使用内存
- _useOffHeap:是否使用堆外内存
- _deserialized:是否序列化
- _replication:副本数,默认为1
详细的存储策略:
NONE | 相当于没持久化的...... |
DISK_ONLY | 将RDD以序列化的形式存储到磁盘上 |
DISK_ONLY_2 | 将RDD以序列化的形式存储到磁盘上,并且副本数为2 |
MEMORY_ONLY | (默认的最优选择)将RDD存储到内存中,不进行序列化 这种策略下,如果需要持久化的数据大于内存空间,将会丢弃多余的部分数据。(spark非常瞧不起磁盘的,按照spark的思想,即使老子重新去取数据重新计算,也比多做一次磁盘IO要快) |
MEMORY_ONLY_2 | 将RDD存储到内存中,不进行序列化,副本数为2 |
MEMORY_ONLY_SER | 将RDD以序列化的形式存储到内存中 |
MEMORY_ONLY_SER_2 | 将RDD以序列化的形式存储到内存中,副本数为2 |
MEMORY_AND_DISK_2 | 将RDD存储到内存和磁盘中,不进行序列化,副本数为2 这种策略下,优先将数据存储到内存中, 内存空间不够时,将数据存储到磁盘上 |
MEMORY_AND_DISK_SER | 将RDD以序列化的形式存储到内存和磁盘中 |
MEMORY_AND_DISK_SER_2 | 将RDD以序列化的形式存储到内存和磁盘中,副本数为2 |
OFF_HEAP | 将RDD以序列化的形式存储到内存、磁盘和堆外内存中 |
对比:序列化虽然可以减少一定的空间占用,但是在使用数据时还需要进行反序列化,也会消耗一定资源。
3 如何选择存储级别
- 如果使用默认的存储级别(MEMORY_ONLY),存储在内存中的 RDD 没有发生溢出,那么就选择默认的存储级别。默认存储级别可以最大程度的提高 CPU 的效率,可以使在 RDD 上的操作以最快的速度运行。
- 如果内存不能全部存储 RDD,可以尝试使用 MEMORY_ONLY_SER,并挑选一个快速序列化库将对象序列化,以节省内存空间。使用这种存储级别,计算速度仍然很快。
- 除了在计算该数据集的代价特别高或者在需要过滤大量数据的情况下,尽量不要将溢出的数据存储到磁盘。因为,重新计算这个数据分区的耗时与从磁盘读取这些数据的耗时差不多。
- 如果想快速还原故障,建议使用多副本存储级别(例如,使用 Spark 作为 web 应用的后台服务,在服务出故障时需要快速恢复的场景下)。所有的存储级别都通过重新计算丢失的数据的方式,提供了完全容错机制。但是多副本级别在发生数据丢失时,不需要重新计算对应的数据库,可以让任务继续运行。
4 删除持久化数据
Spark 自动监控各个节点上的缓存使用率,并以最近最少使用的方式(LRU)将旧数据块移除内存。
如果想手动移除一个 RDD,而不是等待该 RDD 被 Spark 自动移除,可以使用 RDD.unpersist() 方法
5 checkPoint
checkpoint的意思就是建立检查点,类似于快照,例如在spark计算里面计算流程DAG特别长,服务器需要将整个DAG计算完成得出结果,但是如果在这很长的计算流程中突然中间算出的数据丢失了,spark又会根据RDD的依赖关系从头到尾计算一遍,这样子就很费性能,当然我们可以将中间的计算结果通过cache或者persist放到内存或者磁盘中,但是这样也不能保证数据完全不会丢失,存储的这个内存出问题了或者磁盘坏了,也会导致spark从头再根据RDD计算一遍,所以就有了checkpoint,其中checkpoint的作用就是将DAG中比较重要的中间数据做一个检查点将结果存储到一个高可用的地方(通常这个地方就是HDFS里面)
那么checkPoint有什么特点?和cache、persist有什么区别呢?可以先看一下源码中的注释
也就是说,如果把一个RDD标记为检查点,那么这个RDD将会以文件的形式存储指定文件夹,这个文件夹可以通过SparkContext.setCheckpointDir设置,同时会移除所有父级RDD的依赖关系,而且必须在行动算子提交任务之前调用这个函数
强烈推荐在使用checkpoint之前先在内存中做一次持久化,否则需要进行一次重复计算。
6 持久化查看
在程序运行过程中可以以WebUI的形式查看
代码中也有相应的方法可以查看存储级别等。