Spark原始碼分析之Checkpoint機制
對於一個複雜的RDD,我們如果擔心某些關鍵的,會在後面反覆使用的RDD,可能會因為節點的故障,導致持久化資料的丟失,就可以針對該RDD啟動checkpoint機制,實現容錯和高可用。
在進行checkpoint之前,最好先對RDD執行持久化操作,比如persist(StorageLevel.DISK_ONLY)如果持久化了,就不用再重新計算;否則如果沒有持久化RDD,還設定了checkpoint,那麼本來job都結束了,但是由於中間的RDD沒有持久化,那麼checkpoint job想要將RDD資料寫入外部檔案系統,還得從RDD之前的所有的RDD全部重新計算一次,再進行checkpoint。然後從持久化的RDD磁碟檔案讀取資料
一 RDD的checkpoint方法
# 如果SparkContext沒有設定checkpointDir,則丟擲異常
# 如果設定了,則建立RDDCheckpointData,這個類主要負責管理RDD的checkpoint的程式和狀態等
# 建立RDDCheckpointData的時候,會初始化checkpoint狀態為Initialized
def checkpoint(): Unit = RDDCheckpointData.synchronized {
if (context.checkpointDir.isEmpty) {
throw new SparkException("Checkpoint directory has not been set in the SparkContext")
} else if (checkpointData.isEmpty) {
checkpointData = Some(new ReliableRDDCheckpointData(this))
}
}
二persist 持久化RDD
# 如果該RDD已經有了storage level,但是還和指定的storage level不相等,那麼丟擲異常,不支援在一個RDD分配了storage level之後再分配一個storage level
# 標記這個RDD為persisting
# 設定RDD的storage level
private def
persist(newLevel:StorageLevel,
allowOverride: Boolean):this.type
= {
if (storageLevel!=
StorageLevel.NONE&&
newLevel != storageLevel&& !allowOverride) {
throw new UnsupportedOperationException(
"Cannotchange storage level of an RDD after it was already assigned a level")
}
// If this isthe first time this RDD is marked for persisting, register it
// with the SparkContext for cleanupsand accounting. Do this only once.
if (storageLevel==
StorageLevel.NONE) {
sc.cleaner.foreach(_.registerRDDForCleanup(this))
sc.persistRDD(this)
}
storageLevel =
newLevel
this
}
三 RDD的doCheckpoint方法
當呼叫DAGScheduler的runJob的時候,開始呼叫RDD的doCheckpoint方法
# 該rdd是否已經呼叫doCheckpoint,如果還沒有,則開始處理
# 檢視是否需要把該rdd的所有依賴即血緣全部checkpoint,如果需要,血緣上的每一個rdd遞迴呼叫該方法
# 呼叫RDDCheckpointData的checkpoint方法
private[spark] def doCheckpoint(): Unit = {
RDDOperationScope.withScope(sc, "checkpoint", allowNesting = false, ignoreParent = true) {
// 該rdd是否已經呼叫doCheckpoint,如果還沒有,則開始處理
if (!doCheckpointCalled) {
doCheckpointCalled = true
// 判斷RDDCheckpointData是否已經定義了,如果已經定義了
if (checkpointData.isDefined) {
// 檢視是否需要把該rdd的所有依賴即血緣全部checkpoint
if (checkpointAllMarkedAncestors) {
// 血緣上的每一個rdd遞迴呼叫該方法
dependencies.foreach(_.rdd.doCheckpoint())
}
// 呼叫RDDCheckpointData的checkpoint方法
checkpointData.get.checkpoint()
} else {
dependencies.foreach(_.rdd.doCheckpoint())
}
}
}
}
四RDDCheckpointData的checkpoint
# 將checkpoint的狀態從Initialized置為CheckpointingInProgress
# 呼叫子類的doCheckpoint,建立一個新的CheckpointRDD
# 將checkpoint狀態置為Checkpointed狀態,並且改變rdd之前的依賴,設定父rdd為新建立的CheckpointRDD
final def checkpoint(): Unit = {
// 將checkpoint的狀態從Initialized置為CheckpointingInProgress
RDDCheckpointData.synchronized {
if (cpState == Initialized) {
cpState = CheckpointingInProgress
} else {
return
}
}
// 呼叫子類的doCheckpoint,我們以ReliableCheckpointRDD為例,建立一個新的CheckpointRDD
val newRDD = doCheckpoint()
// 將checkpoint狀態置為Checkpointed狀態,並且改變rdd之前的依賴,設定父rdd為新建立的CheckpointRDD
RDDCheckpointData.synchronized {
cpRDD = Some(newRDD)
cpState = Checkpointed
rdd.markCheckpointed()
}
}
五RDDCheckpointData的doCheckpoint
我們以ReliableCheckpointRDD為例,將rdd的資料寫入HDFS中checkpoint目錄,並且建立CheckpointRDD
protected override def doCheckpoint(): CheckpointRDD[T] = {
// 將rdd的資料寫入HDFS中checkpoint目錄,並且建立CheckpointRDD
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
}
六ReliableCheckpointRDD的writeRDDToCheckpointDirectory
將rdd的資料寫入HDFS中checkpoint目錄,並且建立CheckpointRDD
def writeRDDToCheckpointDirectory[T: ClassTag](
originalRDD: RDD[T],
checkpointDir: String,
blockSize: Int = -1): ReliableCheckpointRDD[T] = {
val sc = originalRDD.sparkContext
// 建立checkpoint輸出目錄
val checkpointDirPath = new Path(checkpointDir)
// 獲取HDFS檔案系統API介面
val fs = checkpointDirPath.getFileSystem(sc.hadoopConfiguration)
// 建立目錄
if (!fs.mkdirs(checkpointDirPath)) {
throw new SparkException(s"Failed to create checkpoint path $checkpointDirPath")
}
// 將配置檔案資訊廣播到所有節點
val broadcastedConf = sc.broadcast(
new SerializableConfiguration(sc.hadoopConfiguration))
// 重新啟動一個job,將rdd的分割槽資料寫入HDFS
sc.runJob(originalRDD,
writePartitionToCheckpointFile[T](checkpointDirPath.toString, broadcastedConf) _)
// 如果rdd的partitioner不為空,則將partitioner寫入checkpoint目錄
if (originalRDD.partitioner.nonEmpty) {
writePartitionerToCheckpointDir(sc, originalRDD.partitioner.get, checkpointDirPath)
}
// 建立一個CheckpointRDD,該分割槽數目應該和原始的rdd的分割槽數是一樣的
val newRDD = new ReliableCheckpointRDD[T](
sc, checkpointDirPath.toString, originalRDD.partitioner)
if (newRDD.partitions.length != originalRDD.partitions.length) {
throw new SparkException(
s"Checkpoint RDD $newRDD(${newRDD.partitions.length}) has different " +
s"number of partitions from original RDD $originalRDD(${originalRDD.partitions.length})")
}
newRDD
}
七 RDD的iterator方法
# 當持久化RDD的時候,執行task的時候,會遍歷RDD指定分割槽的資料,在持久的時候,因為指定了storage level,所以我們會呼叫getOrCompute獲取資料,由於第一次還沒有持久化過,所以會先計算。但是資料還沒有被持久化,所以此時先把資料持久化到磁碟(假設持久化時就指定了StorageLevel=DISK_ONLY),然後再把block資料快取到本地記憶體
# 進行checkpoint操作時,會啟動一個新的job來處理checkpoint任務。當執行checkpoint的任務來執行RDD的iterator方法時,此時我們知道該RDD的持久化級別不為空,則從BlockManager獲取出結果來,因為已經持久化過了所以不需要進行計算。如果持久化的資料此時已經丟失呢,怎麼辦呢?即storage level為空了,這此時就會呼叫computeOrReadCheckpoint方法,重新計算結果,然後寫入checkpoint目錄
# 如果已經持久化和checkpoint了,那麼此時如果有任務在iterator獲取不到block,那麼就會呼叫computeOrReadCheckpoint方法,此時已經物化過了,所以直接從原始RDD對應的父RDD(CheckpointRDD)的iterator方法,此時已經沒有持久化級別,所以CheckpointRDD的iterator方法就會呼叫CheckpointRDD的compute方法從checkpoint檔案讀取資料
final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
// 如果StorageLevel不為空,表示該RDD已經持久化過了,可能是在記憶體,也有可能是在磁碟,
// 如果是磁碟獲取的,需要把block快取在記憶體中
if (storageLevel != StorageLevel.NONE) {
getOrCompute(split, context)
} else {
// 進行rdd partition的計算或者根據checkpoint讀取資料
computeOrReadCheckpoint(split, context)
}
}
八 RDD的computeOrReadCheckpoint方法
# 如果checkpoint狀態已經置為checkpointed了,表示checkpoint已經完成,這時候從checkpoint獲取;如果還是checkpointInProgress,則表示持久化資料丟失,或者根本就沒有持久化,所以需要原來的RDD的compute方法重新計算結果
private[spark] def computeOrReadCheckpoint(split: Partition, context: TaskContext): Iterator[T] =
{
// 當前rdd是否已經checkpoint和物化了,如果已經checkpoint,則呼叫父類的CheckpointRDD的iterator方法獲取
// 如果沒有則開始計算
if (isCheckpointedAndMaterialized) {
firstParent[T].iterator(split, context)
} else {
// 則呼叫rdd的compute方法開始計算,返回一個Iterator物件
compute(split, context)
}
}
九CheckpointRDD的compute方法
# 建立checkpoint檔案
#從HDFS上的checkpoint檔案讀取checkpoint過的資料
override def compute(split: Partition, context: TaskContext): Iterator[T] = {
// 建立checkpoint檔案
val file = new Path(checkpointPath, ReliableCheckpointRDD.checkpointFileName(split.index))
// 從HDFS上的checkpoint檔案讀取checkpoint過的資料
ReliableCheckpointRDD.readCheckpointFile(file, broadcastedConf, context)
}
相關文章
- Spark原始碼分析之BlockManager通訊機制Spark原始碼BloC
- spark基礎之spark streaming的checkpoint機制Spark
- Spark原始碼分析之Worker啟動通訊機制Spark原始碼
- Spark RPC框架原始碼分析(三)Spark心跳機制分析SparkRPC框架原始碼
- Spark原始碼分析之MemoryManagerSpark原始碼
- Spark原始碼分析之BlockStoreSpark原始碼BloC
- Spark原始碼分析之DiskBlockMangaer分析Spark原始碼BloC
- Spark原始碼分析之cahce原理分析Spark原始碼
- HDFS 重要機制之 checkpoint
- Spark Shuffle機制詳細原始碼解析Spark原始碼
- MySQL筆記之Checkpoint機制MySql筆記
- 【Zookeeper】原始碼分析之Watcher機制(一)原始碼
- spark 原始碼分析之十三 -- SerializerManager剖析Spark原始碼
- 【Zookeeper】原始碼分析之Watcher機制(三)之ZooKeeper原始碼
- spark 原始碼分析之十八 -- Spark儲存體系剖析Spark原始碼
- spark 原始碼分析之十五 -- Spark記憶體管理剖析Spark原始碼記憶體
- 【Zookeeper】原始碼分析之Watcher機制(二)之WatchManager原始碼
- spark 原始碼分析之十九 -- Stage的提交Spark原始碼
- 【Android原始碼】Handler 機制原始碼分析Android原始碼
- Dubbo 原始碼分析 - SPI 機制原始碼
- React原始碼分析 – 事件機制React原始碼事件
- React原始碼分析 - 事件機制React原始碼事件
- spark 原始碼分析之十六 -- Spark記憶體儲存剖析Spark原始碼記憶體
- Spark 原始碼分析系列Spark原始碼
- HashMap擴容機制原始碼分析HashMap原始碼
- 通過WordCount解析Spark RDD內部原始碼機制Spark原始碼
- JVM原始碼分析之Attach機制實現完全解讀JVM原始碼
- ckpt(checkpoint)機制研究
- Dubbo原始碼解析之SPI機制原始碼
- Android 原始碼分析之旅3 1 訊息機制原始碼分析Android原始碼
- Android 原始碼分析(二)handler 機制Android原始碼
- OkHttp 原始碼分析(二)—— 快取機制HTTP原始碼快取
- 從原始碼分析Hystrix工作機制原始碼
- RecyclerView 原始碼分析(二) —— 快取機制View原始碼快取
- JVMTI Attach機制與核心原始碼分析JVM原始碼
- Android Handler機制使用,原始碼分析Android原始碼
- Android訊息機制原始碼分析Android原始碼
- Flutter 命令本質之 Flutter tools 機制原始碼深入分析Flutter原始碼