1、checkpoint
Spark中对于数据的保存除了持久化操作之外,还提供了一种检查点的机制,检查点(本质是通过将RDD写入Disk做检查点)是为了通过lineage做容错的辅助,lineage过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果之后有节点出现问题而丢失分区,从做检查点的RDD开始重做Lineage,就会减少开销。检查点通过将数据写入到HDFS文件系统实现了RDD的检查点功能。
cache 和 checkpoint 是有显著区别的, 缓存把 RDD 计算出来然后放在内存中,但是RDD 的依赖链(相当于数据库中的redo 日志), 血统不能丢掉, 当某个点某个 executor 宕了,上面cache 的RDD就会丢掉, 需要通过 依赖链重新计算出来, checkpoint 是把 RDD 保存在 HDFS中, 是多副本可靠存储,所以依赖链就可以丢掉了,就斩断了依赖链(血统), 是通过复制实现的高容错。
2、比较适合使用检查点机制
如果存在以下场景,则比较适合使用检查点机制
DAG(有向无环图)中的Lineage过长,如果重算,则开销太大(如在PageRank中)。
在宽依赖上做Checkpoint获得的收益更大。
为当前RDD设置检查点。该函数将会创建一个二进制的文件,并存储到checkpoint目录中,该目录是用SparkContext.setCheckpointDir()设置的。在checkpoint的过程中,该RDD的所有依赖于父RDD中的信息将全部被移出。对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发,懒执行。
3、checkpoint 写流程
RDD checkpoint 过程中会经过以下几个状态,
[ Initialized → marked for checkpointing → checkpointing in progress → checkpointed ]
转换流程如下
data.checkpoint 这个函数调用中, 设置的目录中, 所有依赖的 RDD 都会被删除, 函数必须在 job 运行之前调用执行, 强烈建议 RDD 缓存 在内存中
(又提到一次,千万要注意哟), 否则保存到文件的时候需要从头计算。初始化RDD的 checkpointData 变量为 ReliableRDDCheckpointData。 这时候标记为 Initialized 状态
在所有 job action 的时候, runJob 方法中都会调用 rdd.doCheckpoint , 这个会向前递归调用所有的依赖的RDD, 看看需不需要 checkpoint。 如果需要 checkpoint, 然后调用 checkpointData.get.checkpoint(), 里面标记 状态为 CheckpointingInProgress, 里面调用具体实现类的 ReliableRDDCheckpointData 的 doCheckpoint 方法
doCheckpoint -> writeRDDToCheckpointDirectory, 注意这里会把 job 再运行一次, 如果已经cache 了,就可以直接使用缓存中的 RDD 了
, 就不需要重头计算一遍了(怎么又说了一遍), 这时候直接把RDD, 输出到 hdfs, 每个分区一个文件, 会先写到一个临时文件, 如果全部输出完,进行 rename , 如果输出失败,就回滚delete。
标记 状态为 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目录中不同分区保存下来的文件。
5、案例
RPEL
scala> val rdd = sc.parallelize(1 to 100,5)
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at :24
scala> sc.setCheckpointDir("hdfs://hadoop102:9000/checkpoint")
scala> rdd.checkpoint
scala> rdd.count
res2: Long = 100
scala> val ch1 = sc.parallelize(1 to 2)
ch1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[2] at parallelize at :24
scala> val ch2 = ch1.map(_.toString+"["+System.currentTimeMillis+"]")
ch2: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[3] at map at :26
scala> val ch3 = ch1.map(_.toString+"["+System.currentTimeMillis+"]")
ch3: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[4] at map at :26
scala> ch3.checkpoint
scala> ch2.collect
res4: Array[String] = Array(1[1533633885066], 2[1533633885081])
scala> ch2.collect
res5: Array[String] = Array(1[1533633889716], 2[1533633889722])
scala> ch3.collect
res6: Array[String] = Array(1[1533633899554], 2[1533633899556])
scala> ch3.collect
res7: Array[String] = Array(1[1533633899808], 2[1533633899794])
scala> ch3.collect
res8: Array[String] = Array(1[1533633899808], 2[1533633899794])
scala> ch3.collect
res9: Array[String] = Array(1[1533633899808], 2[1533633899794])
查看HDFS上checkpoint的文件
[root@hadoop103 hadoop-2.8.2]# bin/hadoop fs -ls -R /checkpoint
drwxr-xr-x - yinggu supergroup 0 2018-08-07 17:24 /checkpoint/ce68d190-9ed4-4bf4-93b1-656f6cb2a30c
drwxr-xr-x - yinggu supergroup 0 2018-08-07 17:23 /checkpoint/ce68d190-9ed4-4bf4-93b1-656f6cb2a30c/rdd-0
-rw-r--r-- 3 yinggu supergroup 271 2018-08-07 17:23 /checkpoint/ce68d190-9ed4-4bf4-93b1-656f6cb2a30c/rdd-0/part-00000
-rw-r--r-- 3 yinggu supergroup 271 2018-08-07 17:23 /checkpoint/ce68d190-9ed4-4bf4-93b1-656f6cb2a30c/rdd-0/part-00001
-rw-r--r-- 3 yinggu supergroup 271 2018-08-07 17:23 /checkpoint/ce68d190-9ed4-4bf4-93b1-656f6cb2a30c/rdd-0/part-00002
-rw-r--r-- 3 yinggu supergroup 271 2018-08-07 17:23 /checkpoint/ce68d190-9ed4-4bf4-93b1-656f6cb2a30c/rdd-0/part-00003
-rw-r--r-- 3 yinggu supergroup 271 2018-08-07 17:23 /checkpoint/ce68d190-9ed4-4bf4-93b1-656f6cb2a30c/rdd-0/part-00004
drwxr-xr-x - yinggu supergroup 0 2018-08-07 17:25 /checkpoint/ce68d190-9ed4-4bf4-93b1-656f6cb2a30c/rdd-4
-rw-r--r-- 3 yinggu supergroup 23 2018-08-07 17:25 /checkpoint/ce68d190-9ed4-4bf4-93b1-656f6cb2a30c/rdd-4/part-00000
-rw-r--r-- 3 yinggu supergroup 23 2018-08-07 17:25 /checkpoint/ce68d190-9ed4-4bf4-93b1-656f6cb2a30c/rdd-4/part-00001
总结
(1)通过检查点机制能够把RDD的数据保存到一个非易失存储上,配合HDFS使用,
(2)检查点会切断RDD的血统关系。
(3)如果需要使用,则需要通过sparkcontext设置一个检查点目录: sc.setCheckpointDir("hdfs://master01:9000/checkpoint")
设置完成之后,行手动触发checkpoint进检查点的保存。
(4)检查点机制是懒执行的。