持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第22天,点击查看活动详情
RDD持久化有两种方式: 一是使用Cache缓存,二是设置CheckPoint检查点
Cache缓存
RDD通过Cache 或者 Persist方法 将前面的计算结果缓存,默认情况下会把数据以序列化的形式缓存在JVM的堆内存中。但是需要注意的是,这两个方法被调用时并不会立即对当前的RDD进行缓存,而是等到触发后面的action算子时,才会将该RDD缓存在计算节点的内存中,以并供后面重用。
Cache缓存过程情况如下图所示:
从图中,我们可以明确的看到 只有遇到Action算子才会对 RDD进行缓存,而且每碰到一个Action算子就会产生一个Job,每个Job开始计算的时候总是从它对应的最开始的RDD开始计算,如图中的Job2从缓存的RDD开始计算。
注意:Cache操作只会增加血缘关系,不会改变原来的血缘关系
缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除,RDD的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于RDD的一系列转换,丢失的数据会被重算,由于RDD的各个Partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition。
代码实现如下:
object cache01 {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3. 创建一个RDD,读取指定位置文件:hello world
val lineRdd: RDD[String] = sc.textFile("input1")
//3.1.业务逻辑
val wordRdd: RDD[String] = lineRdd.flatMap(line => line.split(" "))
val wordToOneRdd: RDD[(String, Int)] = wordRdd.map {
word => {
println("************")
(word, 1)
}
}
//3.5 cache缓存前打印血缘关系
println(wordToOneRdd.toDebugString)
//3.4 数据缓存 Cache底层调用的就是persist方法,缓存级别默认用的是MEMORY_ONLY
wordToOneRdd.cache()
//3.2 触发执行逻辑
wordToOneRdd.collect().foreach(println)
//3.5 cache缓存后打印血缘关系 Cache操作会增加血缘关系,不改变原有的血缘关系
println(wordToOneRdd.toDebugString)
println("=============================")
//3.3 再次触发执行逻辑
wordToOneRdd.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
RDD CheckPoint检查点
首先我们先说下,为什么在有了缓存Cache的情况下还要做Checkpoint检查点?
因为Cache只会增加血缘关系,如果血缘依赖过长就会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。
检查点Checkpoint会将RDD中间结果写入磁盘,Checkpoint的数据通常是以二进制文件的形式存储在HDFS等容错、高可用的文件系统中。
同样的,对RDD进行Checkpoint操作并不会马上被执行,必须执行Action操作才能触发。但与缓存不一样的是,检查点为了数据安全,会从血缘关系的最开始重新执行计算一遍。
注意:与Cache缓存不同的是,检查点Checkpoint会切断血缘,即在Checkpoint的过程中,会将该RDD的所有依赖于父RDD中的信息将全部被移除。
Checkpoint设立检查点过程如下图所示:
代码实现如下:
object checkpoint01 {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
// 需要设置路径,否则抛异常:Checkpoint directory has not been set in the SparkContext
sc.setCheckpointDir("./checkpoint1")
//3. 创建一个RDD,读取指定位置文件:hello world
val lineRdd: RDD[String] = sc.textFile("input1")
//3.1.业务逻辑
val wordRdd: RDD[String] = lineRdd.flatMap(line => line.split(" "))
val wordToOneRdd: RDD[(String, Long)] = wordRdd.map {
word => {
(word, System.currentTimeMillis())
}
}
//3.4 数据检查点:针对wordToOneRdd做检查点计算
wordToOneRdd.checkpoint()
//3.2 触发执行逻辑
wordToOneRdd.collect().foreach(println)
// 会立即启动一个新的job来专门的做checkpoint运算
//3.3 再次触发执行逻辑
wordToOneRdd.collect().foreach(println)
Thread.sleep(10000000)
//4.关闭连接
sc.stop()
}
}
缓存和检查点区别
1)Cache缓存只是将数据保存起来,不切断血缘依赖;而Checkpoint检查点切断血缘依赖。
2)Cache缓存的数据通常存储在磁盘、内存等地方,可靠性低;Checkpoint的数据通常存储在HDFS等容错、高可用的文件系统,可靠性高。
3)可以对checkpoint()的RDD使用Cache缓存,这样checkpoint的job只需从Cache缓存中读取数据即可,节省了时间,否则还需要再从头计算一次RDD。
4)如果使用完了缓存,可以通过unpersist()方法释放缓存。