Spark RDD 检查点机制
Spark 中对于数据的保存除了持久化操作之外,还提供了一种检查点的机制,类似于快照,就是将 DAG 中比较重要的中间数据做一个检查点将结果存储到一个高可用的地方(通常这个地方就是HDFS 里面。
为什么要使用 checkpoint?
例如在 Spark 计算里面计算流程 DAG 特别长,服务器需要将整个 DAG 计算完成得出结果。但是如果在这很长的计算流程中突然中间算出的数据丢失了, Spark 又会根据 RDD 的依赖关系从头到尾计算一遍,这样子就很耗费性能。当然我们可以将中间的计算结果通过 cache 或者 persist 放到内存或者磁盘中,但是这样也不能保证数据完全不会丢失。存储的这个内存出问题了或者磁盘坏了,也会导致 Spark 从头再根据 RDD 计算一遍,所以就有了 checkpoint
RDD 持久化和RDD检查点区别
缓存把 RDD 计算出来然后放在内存中,但是RDD 的依赖链(相当于数据库中的redo 日志), 也不能丢掉, 当某个点某个 executor 宕了,上面cache 的RDD就会丢掉, 需要通过 依赖链重放计算出来, 不同的是, checkpoint 是把 RDD 保存在 HDFS中, 是多副本可靠存储,所以依赖链就可以丢掉了,就斩断了依赖链, 是通过复制实现的高容错
Spark RDD 检查点适用场景
如果存在以下场景,则比较适合使用检查点机制:
1).DAG 中的 Lineage 过长,如果重算,则开销太大(如在PageRank中)。
2).在宽依赖上做Checkpoint获得的收益更大。
Spark RDD 检查点使用
为当前 RDD 设置检查点。该函数将会创建一个二进制的文件,并存储到checkpoint目录中,该目录是用 SparkContext.setCheckpointDir() 设置的。在 checkpoint 的过程中,该RDD的所有依赖于父 RDD 中的信息将全部被移除。对RDD进行 checkpoint 操作并不会马上被执行,必须执行Action操作才能触发
scala> sc.setCheckpointDir("hdfs://harvey:9000/checkpoint")
执行上述代码后,会在hdfs中的根目录下创建一个名为checkpoint的目录
/checkpoint/56c714e5-1a0f-4e30-b9a6-e7f6a855677c
scala> sc.setCheckpointDir("hdfs://harvey:9000/checkpoint")
scala> val rdd = sc.parallelize(1 to 10)
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at :24
scala> rdd.checkpoint()
这时hdfs中还是没有数据的,因为checkpoint 是 Transformation 操作,只有遇到Action操作时才会执行
执行Action操作
scala> rdd.sum
res2: Double = 55.0
hadoop执行命令如下命令
[hadoop@harvey ~]$ hadoop fs -ls /checkpoint/56c714e5-1a0f-4e30-b9a6-e7f6a855677c/rdd-0/*
-rw-r--r-- 1 hadoop supergroup 171 2019-02-28 17:16 /checkpoint/56c714e5-1a0f-4e30-b9a6-e7f6a855677c/rdd-0/part-00000
上述中,执行的过程中是执行了两次,sum时初始化rdd,然后求和,checkpoint时,也是先初始化rdd,然后将数据保存到hdfs。所以一般情况下都是先 cache,然后再checkpoint,这样checkpoint就会只走一次流程。
查看checkpoint()源码,源码中也有说明,在checkpoint中建议先cache,且当checkpoint执行成功后,前面所有的RDD依赖都会被销毁
// RDD.scala 部分源码
/**
* Mark this RDD for checkpointing. It will be saved to a file inside the checkpoint
* directory set with `SparkContext#setCheckpointDir` and all references to its parent
* RDDs will be removed. This function must be called before any job has been
* executed on this RDD. It is strongly recommended that this RDD is persisted in
* memory, otherwise saving it on a file will require recomputation.
*/
def checkpoint(): Unit = RDDCheckpointData.synchronized {
// NOTE: we use a global lock here due to complexities downstream with ensuring
// children RDD partitions point to the correct parent partitions. In the future
// we should revisit this consideration.
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))
}
}
scala> sc.setCheckpointDir("hdfs://harvey:9000/checkpoint")
scala> val rdd = sc.parallelize(1 to 10)
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at :24
scala> rdd.cache()
res1: rdd.type = ParallelCollectionRDD[0] at parallelize at :24
scala> rdd.checkpoint()
scala> rdd.sum
res3: Double = 55.0