Spark缓存 之 Collect Cache Persist
三者都有汇聚数据,拉取数据存储的作用,mark一下各自的作用。
Collect:
/**
* Return an array that contains all of the elements in this RDD.
*
* @note This method should only be used if the resulting array is expected to be small, as
* all the data is loaded into the driver's memory.
*/
def collect(): Array[T] = withScope {
val results = sc.runJob(this, (iter: Iterator[T]) => iter.toArray)
Array.concat(results: _*)
}
collect操作将RDD中所有元素转换为Array,一般多用于本地local模式下测试输出使用;集群模式下不推荐使用,正如源码所说,collect操作应该用于数组预期比较小的情况,因为这里数据会加载到dirver端内存中,本地测试时影响不大,但是集群模式下,如果dirver端内存申请太小就很容易oom。
Cache:
/**
* Persist this RDD with the default storage level (`MEMORY_ONLY`).
*/
def cache(): this.type = persist()
cache其实就是persist的最基础的一种模式,可以理解为persist的一个多态,因为源码里persist也有这样一个定义:
/**
* Persist this RDD with the default storage level (`MEMORY_ONLY`).
*/
def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)
可以看到,调用的cache其实对应的就是无参数的persist,这里使用场景一般是缓存一些多次使用且占用空间较小的RDD,有点类似Map Join广播的小表一样,这里 MEMORY_ONLY代表只存放在内存中,所以需要考虑要缓存的RDD大小。
Persist:
/**
* Mark this RDD for persisting using the specified level.
*
* @param newLevel the target storage level
* @param allowOverride whether to override any existing level with the new one
*/
private def persist(newLevel: StorageLevel, allowOverride: Boolean): this.type = {
// TODO: Handle changes of StorageLevel
if (storageLevel != StorageLevel.NONE && newLevel != storageLevel && !allowOverride) {
throw new UnsupportedOperationException(
"Cannot change storage level of an RDD after it was already assigned a level")
}
// If this is the first time this RDD is marked for persisting, register it
// with the SparkContext for cleanups and accounting. Do this only once.
if (storageLevel == StorageLevel.NONE) {
sc.cleaner.foreach(_.registerRDDForCleanup(this))
sc.persistRDD(this)
}
storageLevel = newLevel
this
}
persist相对于cache,提供了更灵活的选择:StorageLevel 即储存水平,第二个参数是否允许覆盖是针对spark任务中修改一个RDD的缓存级别,平常用到的机会比较小,大致说一下有哪些存储水平~
StorageLevel Class 主类
class StorageLevel private(
private var _useDisk: Boolean,
private var _useMemory: Boolean,
private var _useOffHeap: Boolean,
private var _deserialized: Boolean,
private var _replication: Int = 1)
extends Externalizable {
主类里可以看到StorageLevel有5个构造参数,分别为:
_useDisk : 使用硬盘,可以理解为当RDD太大而内存放不下时,会放在HDFS或者其他存储的位置
_useMemory: 使用内存,cache 和 persist() 就是这种模式
_useOffHeap: 使用堆外内存,JVM还不熟悉,后续深挖一下
_deserialized: 反序列化,可以理解为空间不足或者想节省存储空间的做法,所以采用序列化可以缩减内存占用
_replication: 备份数量,这里默认值为1,如果本身任务缓存数据较大,且任务失败再执行的代价比较高,为了提高容错率,可以修改为2,这里常用场景就是大规模任务日志落地时,防止oom,io等错误导致落地失败而再次重启大规模任务而准备
StorageLevel Object 静态类
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 = new StorageLevel(true, true, false, true)
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)
根据上面五个参数,这里静态类给出了多种构造方法,最常用的是MEMORY_ONLY:适用于小数据,可以放在内存中,中大型数据适合MEMORY_AND_DISK_SER,如果任务失败重启代价太高,可以考虑MEMORY_AND_DISK_SER_2。这里序列化会节省空间,但是相对应也会因为序列化和反序列增加cpu的处理时间,因此是MEMORY_AND_DISK_SER还是MEMORY_AND_DISK可以结合不同使用场景灵活操作。
使用方法:
rdd.persist(StorageLevel.MEMORY_AND_DISK_SER_2)
总计下使用场景:
1.本地测试多用于 collect
2.RDD数据量不大 cache
3.RDD数据量较大 Cpu不足 persist(MEMORY_AND_DISK) 重启代价高替换为 MEMORY_AND_DISK_2
4.RDD数据量较大 Cpu充足 persist(MEMORY_AND_DISK_SER) 重启代价高替换为 MEMORY_AND_DISK_SER_2
常用场景就是这些,在一些RDD需要多次复用时可以考虑采用上述操作,但MEMORY模式下容易出现OOM,DISK模式下则会因为磁盘之间IO而增加运行的时长,这些都是需要考虑的元素,最后记得用完RDD之后调用unpersist释放多余的空间。
来源:oschina
链接:https://my.oschina.net/u/4407031/blog/4329444