深入理解 spark 的checkpoint 機制

我們應該都很熟悉 checkpoint 這個概念, 就是把內存中的變化刷新到持久存儲,斬斷依賴鏈 在存儲中 checkpoint 是一個很常見的概念, 舉幾個例子:

  1. 數據庫 checkpoint 過程中一般把內存中的變化進行持久化到物理頁, 這時候就可以斬斷依賴鏈, 就可以把 redo 日誌刪掉了, 然後更新下檢查點,
  2. hdfs namenode 的元數據 editlog,  Secondary namenode 會把 edit log 應用到 fsimage, 然後刷到磁盤上, 也相當於做了一次 checkpoint, 就可以把老的 edit log 刪除了。
  3. spark streaming 中對於一些 有狀態的操作, 這在某些 stateful 轉換中是需要的,在這種轉換中,生成 RDD 需要依賴前面的 batches,會導致依賴鏈隨着時間而變長。爲了避免這種沒有盡頭的變長,要定期將中間生成的 RDDs 保存到可靠存儲來切斷依賴鏈, 必須隔一段時間進行一次進行一次 checkpoint。

1、Cache和Checkpoint的區別

There is a significant difference between cache and checkpoint. Cache materializes the RDD 
and keeps it in memory and/or disk. But the lineage of RDD (that is, seq of operations that 
generated the RDD) will be remembered, so that if there are node failures and parts of the 
cached RDDs are lost, they can be regenerated. However, checkpoint saves the RDD to an HDFS 
file and actually forgets the lineage completely. This is allows long lineages to be 
truncated and the data to be saved reliably in HDFS (which is naturally fault tolerant by replication).

翻譯下:

      cache 和 checkpoint 是有顯著區別的,  緩存把 RDD 計算出來然後放在內存中, 但是RDD 的依賴鏈(相當於數據庫中的redo 日誌), 也不能丟掉, 當某個點某個 executor 宕了, 上面cache 的RDD就會丟掉, 需要通過 依賴鏈重放計算出來, 不同的是, checkpoint 是把 RDD 保存在 HDFS中, 是多副本可靠存儲,所以依賴鏈就可以丟掉了,就斬斷了依賴鏈, 是通過複製實現的高容錯。

很好理解:

但是有一點要注意, 因爲checkpoint是需要把 job 重新從頭算一遍, 最好先cache一下, checkpoint就可以直接保存緩存中的 RDD 了, 就不需要重頭計算一遍了, 對性能有極大的提升。

 

2、checkpoint的使用方法

使用checkpoint對RDD做快照大體如下:

sc.setCheckpointDir(checkpointDir.toString)
val rdd = sc.makeRDD(1 to 20, numSlices = 1)
rdd.checkpoint()

      使用很簡單, 就是設置一下  checkpoint 目錄,然後再rdd上調用 checkpoint 方法, action 的時候就對數據進行了 checkpoint

 

3、checkpoint 寫流程

RDD checkpoint 過程中會經過以下幾個狀態:

       [ Initialized → marked for checkpointing → checkpointing in progress → checkpointed ]  

  1. data.checkpoint 這個函數調用中, 設置的目錄中, 所有依賴的 RDD 都會被刪除, 函數必須在 job 運行之前調用執行, 強烈建議 RDD 緩存 在內存中(又提到一次,千萬要注意喲), 否則保存到文件的時候需要從頭計算。初始化RDD的 checkpointData 變量爲 ReliableRDDCheckpointData。  這時候標記爲 Initialized 狀態,
  2. 在所有 job action 的時候, runJob 方法中都會調用 rdd.doCheckpoint ,  這個會向前遞歸調用所有的依賴的RDD, 看看需不需要  checkpoint 。 需要需要 checkpoint, 然後調用  checkpointData.get.checkpoint(), 裏面標記 狀態爲 CheckpointingInProgress,  裏面調用具體實現類的 ReliableRDDCheckpointData 的 doCheckpoint 方法,
  3. doCheckpoint -> writeRDDToCheckpointDirectory, 注意這裏會把 job 再運行一次, 如果已經cache 了,就可以直接使用緩存中的 RDD 了, 就不需要重頭計算一遍了(怎麼又說了一遍),  這時候直接把RDD, 輸出到 hdfs, 每個分區一個文件, 會先寫到一個臨時文件, 如果全部輸出完,進行 rename , 如果輸出失敗,就回滾delete。
  4. 標記 狀態爲 Checkpointed, markCheckpointed 方法中清除所有的依賴, 怎麼清除依賴的呢, 就是 吧RDD 變量的強引用 設置爲 null, 垃圾回收了, 這個後面我們也知道,會觸發 ContextCleaner 裏面監聽清除實際 BlockManager 緩存中的數據

4、checkpoint 讀流程

           如果一個RDD 我們已經 checkpoint了那麼是什麼時候用呢, checkpoint 將 RDD 持久化到 HDFS 或本地文件夾,如果不被手動 remove 掉,是一直存在的,也就是說可以被下一個 driver program 使用。 比如 spark streaming 掛掉了, 重啓後就可以使用之前 checkpoint 的數據進行 recover  ,  當然在同一個 driver program 也可以使用。 我們講下在同一個 driver program 中是怎麼使用 checkpoint 數據的。

       如果 一個 RDD 被checkpoint了, 如果這個 RDD 上有 action 操作時候,或者回溯的這個 RDD 的時候,這個 RDD 進行計算的時候,裏面判斷如果已經 checkpoint 過,  對分區和依賴的處理都是使用的 RDD 內部的 checkpointRDD 變量。

具體細節如下:

      如果 一個 RDD 被checkpoint了,  那麼這個 RDD 中對分區和依賴的處理都是使用的 RDD 內部的 checkpointRDD 變量, 具體實現是  ReliableCheckpointRDD 類型。 這個是在 checkpoint 寫流程中創建的。依賴和獲取分區方法中先判斷是否已經checkpoint, 如果已經checkpoint了, 就斬斷依賴,  使用ReliableCheckpointRDD, 來處理依賴和獲取分區。
如果沒有,才往前回溯依賴。  依賴就是沒有依賴, 因爲已經斬斷了依賴, 獲取分區數據就是讀取 checkpoint 到 hdfs目錄中不同分區保存下來的文件。

整個 checkpoint 讀流程就完了。

 

 

發佈了93 篇原創文章 · 獲贊 107 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章