博客地址: http://blog.youkuaiyun.com/yueqian_zhu/
RDD有cache和persist方法,用于将RDD进行缓存。
cache方法其实就是persist(MEMORY_ONLY)
我们看一下这个方法干了什么事情
def persist(newLevel: StorageLevel): this.type = {
// TODO: Handle changes of StorageLevel
if (storageLevel != StorageLevel.NONE && newLevel != storageLevel) {
throw new UnsupportedOperationException(
"Cannot change storage level of an RDD after it was already assigned a level")
}
sc.persistRDD(this)
// Register the RDD with the ContextCleaner for automatic GC-based cleanup
sc.cleaner.foreach(_.registerRDDForCleanup(this))
storageLevel = newLevel
this
}
/**
* Register an RDD to be persisted in memory and/or disk storage
*/
private[spark] def persistRDD(rdd: RDD[_]) {
_executorAllocationManager.foreach { _ =>
logWarning(
s"Dynamic allocation currently does not support cached RDDs. Cached data for RDD " +
s"${rdd.id} will be lost when executors are removed.")
}
persistentRdds(rdd.id) = rdd
}
相当简单,只是将我们设定的level记录到storageLevel中,以及保存这个rdd及rddId的映射到persistentRdds中,这里还看不出有什么作用!
以前讲到过,action触发后,会执行iterator方法从而获得指定分区的数据迭代器。
final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
if (storageLevel != StorageLevel.NONE) {
SparkEnv.get.cacheManager.getOrCompute(this, split, context, storageLevel)
} else {
computeOrReadCheckpoint(split, context)
}
}
如果这个rdd之前我们设置过cache或者persist,这里就执行if语句中的内容。
如果没有设置过,才从checkpoint中读取;如果没有checkpoint,才重新计算。
if语句中的代码比较多,就不贴代码纯发挥口才了。。
1、用rddID和partition组合成RDDBlockId作为blockmanager中的key,我们就是根据这个key从blockmanager中获取数据(如果之前有缓存的话)
2、如果没有数据,就执行计算,然后将结果存到blockmanager中。
3、如果设置的level不是存内存的,就调用blockmanager的putIterator方法真正存储数据。
4、如果设置的level是存内存的,且可以全部放入内存中,那就这么干吧;如果不能全部放入内存中,且也设置了Disk存储,就全部放入Disk中,否则就不存了呗
在需要做checkpoint之前,需要调用RDD.checkpoint()来mark一下先,不然是不起作用的。并且最好选择persist这个RDD,不然的话会重新计算之前的所有rdd,因为checkpoint操作也是一个新的runJob的过程。
在SparkContext.runJob中, 最后会调用rdd.doCheckpoint() ,所以,mark必须在run job之前完成。