Spark持久化
众所周知,RDD只会保留血缘关系,不存储计算结果。如果想要让计算结果持久化存储,那就要引入cache和persist方法。
提前感受变化
禁用持久化
package com.pzb.rdd.persist
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @Description TODO
* @author 海绵先生
* @date 2023/4/9-16:26
*/
object Spark01_RDD_Persist {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("PersistTest")
val sc = new SparkContext(conf)
val rdd: RDD[String] = sc.makeRDD(List("Hello World", "Hello Spark"))
val flatRDD: RDD[String] = rdd.flatMap(_.split(" "))
val mapRDD: RDD[(String, Int)] = flatRDD.map(word => {
println("@@@@@@")
(word, 1)
})
// TODO 禁用持久化
// mapRDD.cache()
val reduceRDD: RDD[(String, Int)] = mapRDD.reduceByKey(_ + _)
reduceRDD.collect().foreach(println)
println("*************")
val groupRDD: RDD[(String, Iterable[Int])] = mapRDD.groupByKey()
groupRDD.collect().foreach(println)
sc.stop()
/* 整体输出结果
@@@@@@
@@@@@@
@@@@@@
@@@@@@
(Hello,2)
(World,1)
(Spark,1)
*************
@@@@@@
@@@@@@
@@@@@@
@@@@@@
(Hello,CompactBuffer(1, 1))
(World,CompactBuffer(1))
(Spark,CompactBuffer(1))
*/
}
}
启用持久化
package com.pzb.rdd.persist
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @Description TODO
* @author 海绵先生
* @date 2023/4/9-16:26
*/
object Spark02_RDD_Persist {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("PersistTest")
val sc = new SparkContext(conf)
val rdd: RDD[String] = sc.makeRDD(List("Hello World", "Hello Spark"))
val flatRDD: RDD[String] = rdd.flatMap(_.split(" "))
val mapRDD: RDD[(String, Int)] = flatRDD.map(word => {
println("@@@@@@")
(word, 1)
})
// TODO 启用持久化
mapRDD.cache()
val reduceRDD: RDD[(String, Int)] = mapRDD.reduceByKey(_ + _)
reduceRDD.collect().foreach(println)
println("*************")
val groupRDD: RDD[(String, Iterable[Int])] = mapRDD.groupByKey()
groupRDD.collect().foreach(println)
sc.stop()
/* 整体输出结果
@@@@@@
@@@@@@
@@@@@@
@@@@@@
(Hello,2)
(World,1)
(Spark,1)
*************
(Hello,CompactBuffer(1, 1))
(World,CompactBuffer(1))
(Spark,CompactBuffer(1))
*/
}
}
可以看到:因为将中间的计算结果持久化的保存了起来,所以不再需要重新计算以前的步骤。
cache和persist方法
可以看到,cache底层调用的是persist方法
默认是写进内存的。
如果想要写到磁盘或者其他地方,需要调用persist方法。
具体存储方式有很多种:
该保存的路径都是临时路径,当程序结束后是会删除的。如果想永久保留,涉及到了checkpoint的知识了。
checkpoint
checkpoint与cache和persist的效果类似,都是将中间计算结果保存。
checkpoint与cache和persist的不同之处就在于:checkpoint在作业执行结束时不会删除保存的文件,而cache和persist会删除临时文件。
package com.pzb.rdd.persist
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @Description TODO
* @author 海绵先生
* @date 2023/4/9-16:26
*/
object Spark03_RDD_Persist {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("PersistTest")
val sc = new SparkContext(conf)
val rdd: RDD[String] = sc.makeRDD(List("Hello World", "Hello Spark"))
val flatRDD: RDD[String] = rdd.flatMap(_.split(" "))
val mapRDD: RDD[(String, Int)] = flatRDD.map(word => {
println("@@@@@@")
(word, 1)
})
// checkpoint 需要落盘 需要通过sc.setCheckPoint()方法指定检查的的保存路径
// 检查点路径保存的文件,当作业执行完毕后,不会被删除
// 一般路径保存都是分布式存储文件中(HDFS),这里为方便就保存在本地吧
sc.setCheckpointDir("cp")
mapRDD.checkpoint()
val reduceRDD: RDD[(String, Int)] = mapRDD.reduceByKey(_ + _)
reduceRDD.collect().foreach(println)
println("*************")
val groupRDD: RDD[(String, Iterable[Int])] = mapRDD.groupByKey()
groupRDD.collect().foreach(println)
sc.stop()
/* 整体输出结果
@@@@@@
@@@@@@
@@@@@@
@@@@@@
@@@@@@
@@@@@@
@@@@@@
@@@@@@
(Hello,2)
(World,1)
(Spark,1)
*************
(Hello,CompactBuffer(1, 1))
(World,CompactBuffer(1))
(Spark,CompactBuffer(1))
*/
}
}
可以看到:虽然星号下的数据没有变化,但@@@@@@输出了很多遍。
这是因为checkpoint在执行,会同时生成一个Job,重新计算一遍。
为了避免给这种情况的发生,一般checkpoint会跟cache搭配使用,让checkpoint从cache中获取数据
checkpoint结合cache使用
package com.pzb.rdd.persist
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @Description TODO
* @author 海绵先生
* @date 2023/4/9-16:26
*/
object Spark03_RDD_Persist {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("PersistTest")
val sc = new SparkContext(conf)
val rdd: RDD[String] = sc.makeRDD(List("Hello World", "Hello Spark"))
val flatRDD: RDD[String] = rdd.flatMap(_.split(" "))
val mapRDD: RDD[(String, Int)] = flatRDD.map(word => {
println("@@@@@@")
(word, 1)
})
// checkpoint 需要落盘 需要通过sc.setCheckPoint()方法指定检查的的保存路径
// 检查点路径保存的文件,当作业执行完毕后,不会被删除
// 一般路径保存都是分布式存储文件中(HDFS),这里为方便就保存在本地吧
sc.setCheckpointDir("cp")
// checkpoint搭配cache使用,避免checkpoint从新的Job中拿数据
mapRDD.cache()
mapRDD.checkpoint()
val reduceRDD: RDD[(String, Int)] = mapRDD.reduceByKey(_ + _)
reduceRDD.collect().foreach(println)
println("*************")
val groupRDD: RDD[(String, Iterable[Int])] = mapRDD.groupByKey()
groupRDD.collect().foreach(println)
sc.stop()
/* 整体输出结果
@@@@@@
@@@@@@
@@@@@@
@@@@@@
(Hello,2)
(World,1)
(Spark,1)
*************
(Hello,CompactBuffer(1, 1))
(World,CompactBuffer(1))
(Spark,CompactBuffer(1))
*/
}
}
三者区别?
cache :将数据临时的存储在内存中进行数据重用,会在血缘关系中添加新的依赖。一旦,出现问题,可以重头读取数据
persist :将数据临时存储在磁盘文件中进行数据重用,涉及到磁盘IO,性能较低,但是数据安全。如果作业执行完毕,临时保存的数据文件就会丢失
checkpoint :将数据长久地保存在磁盘文件中进行数据重用,涉及到醛盘IO,性能较低,但是数据交全。为了保证数据安全,所以一殷情况下,会独立执行作业为了能够提高效率,一殷情况下,是需要和cache联合使用,执行过程中,会切断血缘关系。重新建立新的血缘关系。checkpoint等同于改变数据源。