通过之前章节,我们了解到RDD包含多个partition,每个Partition对应一个数据块Block,那么每个RDD中包含一个或多个数据块Block,每个Block拥有唯一的BlockId,对应数据块编号规则为:"rdd_" + rddId + "_" + splitIndex,其中splitIndex为该数据块对应Partition的序列号。
RDD存储调用
在存储级别一章中,我们知道在persist方法中并没有发生数据存储操作动作,实际发生数据操作是在任务运行过程中,RDD调用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)
}
}
getOrCompute方法是存储逻辑的核心,代码如下:
def getOrCompute[T](
rdd: RDD[T],
partition: Partition,
context: TaskContext,
storageLevel: StorageLevel): Iterator[T] = {
// 通过RDD的编号和Partition序号获取数据块Block的编号
val key = RDDBlockId(rdd.id, partition.index)
logDebug(s"Looking for partition $key")
// 通过BlockMananger获取Block数据
blockManager.get(key) match {
// 该结果表示获取数据成功,并记录结果度量信息
case Some(blockResult) =>
// Partition is already materialized, so just return its values
val existingMetrics = context.taskMetrics
.getInputMetricsForReadMethod(blockResult.readMethod)
existingMetrics.incBytesRead(blockResult.bytes)
val iter = blockResult.data.asInstanceOf[Iterator[T]]
new InterruptibleIterator[T](context, iter) {
override def next(): T = {
existingMetrics.incRecordsRead(1)
delegate.next()
}
}
// 如果数据块不存在,则尝试读取检查点结果进行迭代计算
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.isDefined) {
return new InterruptibleIterator[T](context, storedValues.get)
}
// Otherwise, we have to load the partition ourselves
try {
logInfo(s"Partition $key not found, computing it")
val computedValues = rdd.computeOrReadCheckpoint(partition, context)
// If the task is running locally, do not persist the result
if (context.isRunningLocally) {
return computedValues
}
// Otherwise, cache the values and keep track of any updates in block statuses
val updatedBlocks = new ArrayBuffer[(BlockId, BlockStatus)]
val cachedValues = putInBlockManager(key, computedValues, storageLevel, updatedBlocks)
val metrics = context.taskMetrics
val lastUpdatedBlocks = metrics.updatedBlocks.getOrElse(Seq[(BlockId, BlockStatus)]())
metrics.updatedBlocks = Some(lastUpdatedBlocks ++ updatedBlocks.toSeq)
new InterruptibleIterator(context, cachedValues)
} finally {
loading.synchronized {
loading.remove(key)
loading.notifyAll()
}
}
}
}
读数据过程
BlockManager的get方法是读数据的入口点,在读取时分为本地读取和远程节点读取两个步骤。本地读取使用getLocal方法,在该方法中根据不同的存储级别直接调用不同存储实现的方法;而远程节点读取使用getRemote方法,调用远程数据传输服务类BlockTransferService的fetchBlockSync进行处理,使用Netty的fetchBlocks方法获取数据。整个数据读取类调用如下图所示:
后续补上
写数据过程
BlockManager的putIterator方法是写数据的入口点。整个数据写入类调用关系如下图所示:
后续补上