检查点是很多分布式系统为了容灾容错引入的机制,其实质是将系统运行期的内存数据结构和状态持久化到磁盘上,在需要时通过对这些持久化数据的读取,重新构造出之前的运行期状态。Spark 使用检查点主要是为了将 RDD 的执行状态保留下来,在重新执行时就不用重新计算,而直接从检查点读取。CheckpointRDD 是对检查点数据进行操作的 RDD,例如,读写检查点数据。RDDCheckpointData 表示 RDD 检查点的数据。Spark 的检查点离不开 CheckpointRDD 和 RDDCheckpointData 的支持,本节将对它们的代码实现进行分析。
CheckpointRDD 的实现
CheckpointRDD 是特殊的 RDD,用来从存储体系中恢复检查点数据。CheckpointRDD 的定义如下。
private[spark] abstract class CheckpointRDD[T: ClassTag](sc: SparkContext)
extends RDD[T](sc, Nil) {
override def doCheckpoint(): Unit = { }
override def checkpoint(): Unit = { }
override def localCheckpoint(): this.type = this
protected override def getPartitions: Array[Partition] = ???
override def compute(p: Partition, tc: TaskContext): Iterator[T] = ???
}
可以看到 CheckpointRDD 重写了 RDD 的 5 个方法,分别如下。
- doCheckpoint:保存检查点。
- checkpoint:读取检查点数据。
- localCheckpoint:本地的检查点。
- getPartitions:获取检查点的分区数组。
- compute:名为计算,实际是从检查点恢复数据。
CheckpointRDD 有 LocalCheckpointRDD 和 ReliableCheckpointRDD 两个子类,它们都没有完全实现 CheckpointRDD 中的方法。下面以 ReliableCheckpointRDD为例,来介绍 Check-pointRDD 的具体实现。
LocalCheckpointRDD
ReliableCheckpointRDD
ReliableCheckpointRDD 中定义了以下属性。
- sc:即 SparkContext。
- checkpointPath: 检查点目录的字符串表示。
- _partitioner: 调用方指定的分区计算器,默认为 None。
- hadoopConf: SparkContext 的_hadoopConfiguration 属性,即 Hadoop 的配置信息。
- cpath: 类型为 org.apache.hadoop.fs.Path,表示 checkpointPath 对应的 Hadoop 文件系统中的路径。
- fs: 使用 hadoopConf 得到的 org.apache.hadoop.fs.FileSystem。
- broadcastedConf: 调用 SparkContext 的 broadcast 方法对 hadoopConf 进行广播后返回的 Broadcast 对象。
- partitioner: ReliableCheckpointRDD 的分区计算器,优先采用_partitioner指定的,否则调用 ReliableCheckpointRDD 的伴生对象的readCheckpointedPartitionerFile 方法从 checkpointPath 指定的检查点目录下读取分区计算器。
ReliableCheckpointRDD 的伴生对象中提供了很多工具方法,下面逐个介绍。
writePartitionToCheckpointFile方法
writePartitionToCheckpointFile 方法用于将 RDD 分区的数据写入到检查点目录下的文件中
writePartitionerToCheckpointDir方法
writePartitionerToCheckpointDir 方法用于将分区计算器的数据写入到检查点的目录下
writeRDDToCheckpointDirectory方法
writeRDDToCheckpointDirectory 方法用于将 RDD 的数据写入检查点目录
riteRDDToCheckpointDirectory 方法的执行步骤如下。
- 调用 SparkContext 的 runJob 方法将 RDD 的数据写入到检查点目录。将数据写入磁盘的函数是 ReliableCheckpointRDD 的伴生对象的writePartitionToCheckpointFile 方法
- 如果 RDD 有分区计算器,那么调用 ReliableCheckpointRDD 的伴生对象的write-PartitionerToCheckpointDir 方法将分区计算器的信息也写入到检查点目录。
- 创建并返回 ReliableCheckpointRDD。
readCheckpointFile方法
readCheckpointFile 方法用于从检查点目录下的文件中读取 RDD 的数据
上面介绍了 ReliableCheckpointRDD 的伴生对象提供的方法,下面介绍ReliableCheck-pointRDD 对父类 RDD 重写的部分方法。
getPartitions方法
ReliableCheckpointRDD 实现的 getPartitions 方法用于从检查点文件中获取分区数组。
getPreferredLocations方法
ReliableCheckpointRDD 实现的 getPreferredLocations 方法用于从检查点文件中获取偏好位置。
compute方法
ReliableCheckpointRDD 实现的 compute 方法用于从检查点文件中获取检查点数据。
override def compute(split: Partition, context: TaskContext): Iterator[T] = {
val file = new Path(checkpointPath, ReliableCheckpointRDD.checkpointFile-Name(split.index))
ReliableCheckpointRDD.readCheckpointFile(file, broadcastedConf, context)
}
根据对 CheckpointRDD 的各个方法的分析,检查点似乎不再那么神秘,其实现不过是对文件的读与写。
RDDCheckpointData 的实现
RDDCheckpointData 用于保存与检查点相关的信息。每个 RDDCheckpointData实例都与一个 RDD 实例相关联。RDDCheckpointData 中一共有三个属性。
- rdd:RDDCheckpointData 关联的 RDD。
- cpState:检查点的状态。默认为 Initialized。cpState 的值来自枚举类型Checkpoint-State,CheckpointState 中定义了检查点的状态,包括初始化完成(Initialized)、正在保存检查点(CheckpointingInProgress)和保存检查点完毕(Checkpointed)。
- cpRDD:保存检查点数据的 RDD,即 CheckpointRDD 的实现类。
RDDCheckpointData 中定义了一些方法,分别如下。
isCheckpointed方
isCheckpointed 方法用于判断是否已经为 RDDCheckpointData 关联的 RDD 保存了检查点数据,其实现如下。
def isCheckpointed: Boolean = RDDCheckpointData.synchronized {
cpState == Checkpointed
}
checkpoint
heckpoint 方法是用于将 RDDCheckpointData 关联的 RDD 的数据保存到检查点的模板方法
final def checkpoint(): Unit = {
RDDCheckpointData.synchronized {
if (cpState == Initialized) {
cpState = CheckpointingInProgress
} else {
return
}
}
val newRDD = doCheckpoint()
// Update our state and truncate the RDD lineage
RDDCheckpointData.synchronized {
cpRDD = Some(newRDD)
cpState = Checkpointed
rdd.markCheckpointed()
}
}
checkpoint 方法的执行步骤如下。
- 如果检查点的状态是 Initialized,那么将 cpState 设置为CheckpointingInProgress,否则返回。
- 调用 doCheckpoint 方法保存检查点并生成 CheckpointRDD。doCheckpoint方法需要 RDDCheckpointData 的子类实现。RDDCheckpointData 的子类有LocalRDDCheckpointData 和 ReliableRDDCheckpointData 两种,10.3.3 节以 ReliableRDDCheckpointData 为例,介绍了其实现的 doCheckpoint 方法。
- 由 cpRDD 持有刚生成的 CheckpointRDD,然后将 cpState 设置为Checkpointed,最后调用 RDD 的 markCheckpointed 方法(见代码清单 10-12)清空依赖。之所以清空依赖,是因为现在已经有了 CheckpointRDD,之前的依赖关系不再需要了。
标记 RDD 已经保存了检查点
private[spark] def markCheckpointed(): Unit = {
clearDependencies()
partitions_ = null
deps = null // Forget the constructor argument for dependencies too
}
protected def clearDependencies() {
dependencies_ = null
}
checkpointRDD
checkpointRDD 方法用于获取 cpRDD 持有的 CheckpointRDD,其实现如下。
def checkpointRDD: Option[CheckpointRDD[T]] = RDDCheckpointData.synchronized { cpRDD }
getPartitions
getPartitions 方法用于获取 CheckpointRDD 的分区数组,
def getPartitions: Array[Partition] = RDDCheckpointData.synchronized {
cpRDD.map(_.partitions).getOrElse { Array.empty }
}
有了对 RDDCheckpointData 的理解,下一小节以 RDDCheckpointData 的子类Reliable-RDDCheckpointData 为例,来看看 RDDCheckpointData 该如何实现。
ReliableRDDCheckpointData 的实现
本节以 RDDCheckpointData 的子类 ReliableRDDCheckpointData 为例,来看看RDD-CheckpointData 的具体实现。
ReliableRDDCheckpointData 除继承了 RDDCheckpointData 的属性外,还有自身的一个属性 cpDir。cpDir 是保存 ReliableRDDCheckpointData 所关联的 RDD 数据的检查点目录,是通过调用 ReliableRDDCheckpointData 的伴生对象的checkpointPath 方法生成的。
ReliableRDDCheckpointData 及其伴生对象提供了以下方法。
checkpointPath
checkpointPath 方法用于在 SparkContext 的 checkpointDir 属性指定的 RDD 计算过程中保存检查点的目录下创建子目录,作为保存 ReliableRDDCheckpointData所关联的 RDD 数据的检查点目录。ReliableRDDCheckpointData 的伴生对象提供的 checkpointPath 方法。
def checkpointPath(sc: SparkContext, rddId: Int): Option[Path] = {
sc.checkpointDir.map { dir => new Path(dir, s"rdd-$rddId") }
}
doCheckpoint
ReliableRDDCheckpointData 实现了父类 RDDCheckpointData 定义的doCheckpoint 方法如代码
protected override def doCheckpoint(): CheckpointRDD[T] = {
val newRDD = ReliableCheckpointRDD.writeRDDToCheckpointDirectory(rdd, cpDir)
if (rdd.conf.getBoolean("spark.cleaner.referenceTracking.cleanCheckpoints", false)) {
rdd.context.cleaner.foreach { cleaner =>
cleaner.registerRDDCheckpointDataForCleanup(newRDD, rdd.id)
}
}
logInfo(s"Done checkpointing RDD ${rdd.id} to $cpDir, new parent is RDD ${newRDD.id}")
newRDD
}
ReliableRDDCheckpointData 实现的 doCheckpoint 方法的执行步骤如下。
- 调用 ReliableCheckpointRDD 的伴生对象的writeRDDToCheckpointDirectory 方法(见代码清单 10-6)将 RDD 的数据保存到检查点目录。
- 如果 spark.cleaner.referenceTracking.cleanCheckpoints 属性指定为 true,那么将生成的 ReliableCheckpointRDD 注册到 SparkContext 的子组件ContextCleaner 的 referenceBuffer 中,以便于 ContextCleaner 对ReliableCheckpointRDD 进行清理。
- 返回生成的 ReliableCheckpointRDD。