如果说spark是一把在大数据处理领域的快刀,那么spark的存储系统设计及管理等相关模块就算不是刀尖,也算得上十分锋利的刀锋了,由于工作需要,我会伴着源码来深入学习一下,这里做一个记录备忘。
RDD的cache和persist
谈到spark存储,第一反应先想到了RDD里的cache和persist。如果从RDD中的cache方法作为入口来看,cache与persist殊途同归,无非是persist支持可配的storage level作为入参,而cache直接就是默认了MEMORY_ONLY。核心是三个步骤,一个是把RDD内的storageLevel字段设置一下,另一个是在sparkcontext中标记一下,最后注册一下清理任务。最后一步先不看,仔细跟了一下在sparkcontext中标记这个动作,可以看到这个记录除了调试测试时候观察一下以外在核心流程中实际上不会用到这个标记,那么“可疑分子”就是RDD内的storageLevel这个属性了。另外,通过代码可以清楚看到,这个storageLevel只能被设置一次,换句话说就是一旦从NONE改为了一个值后就不能被修改了,否则会报错。
然后哪里会用到这个可疑分子呢,跟一把代码,看一下RDD任务计算的核心函数之一:RDD里藏着的迭代器函数:
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)
}
}
if (storageLevel != StorageLevel.NONE) {
SparkEnv.get.cacheManager.getOrCompute(this, split, context, storageLevel)
}else {
computeOrReadCheckpoint(split, context)
}
}
很明显,如果storageLevel不是NONE的话就通过CacheManager的getOrCompute来搞起啦。
CacheManager
这货是藏在SparkEnv里的诸多大神之一,这里我们来好好看看。先简单介绍一下,CacheManager负责将RDD的分区数据传递到BlockManager,并且保证一个节点一次不会保存两个副本。
CacheManager里的核心方法就是刚才看到过的getOrCompute方法,这里粗粗看一下:
/** Gets or computes an RDD partition. Used by RDD.iterator() when an RDD is cached. */
def getOrCompute[T](
rdd: RDD[T],
partition: Partition,
context: TaskContext,
storageLevel: StorageLevel): Iterator[T] = {
val key = RDDBlockId(rdd.id, partition.index)
logDebug(s"Looking for partition $key")
blockManager.get(key) match {
case Some(blockResult) =>
// Partition is already materialized, so just return its values
context.taskMetrics.inputMetrics = Some(blockResult.inputMetrics)
new InterruptibleIterator(context, blockResult.data.asInstanceOf[Iterator[T]])
case None =>
// Acquire a lock for loading this partition
// If another thread already holds the lock, wait for it to finish return its results
val storedValues = acquireLockForPartition[T](key)
if (storedValues.isDe