Spark RDD 是惰性求值的,而有时我们希望能多次使用同一个 RDD。如果简单地对 RDD 调用行动操作,Spark 每次都会重算 RDD 以及它的所有依赖。这在迭代算法中消耗格外大,因为迭代算法常常会多次使用同一组数据。
比如下面就是先对 RDD 作一次计数、再把该 RDD 输出的一个小例子。
val result = input.map(x => x*x)
println(result.count())
println(result.collect().mkString(","))
为了避免多次计算同一个 RDD,可以让 Spark 对数据进行持久化。当我们让 Spark 持久化存储一个 RDD 时,计算出 RDD 的节点会分别保存它们所求出的分区数据。如果一个有持久化数据的节点发生故障,Spark 会在需要用到缓存的数据时重算丢失的数据分区。如果希望节点故障的情况不会拖累我们的执行速度,也可以把数据备份到多个节点上。
出于不同的目的,我们可以为 RDD 选择不同的持久化级别(如下表)。在 Scala和 Java 中,默认情况下 persist() 会把数据以序列化的形式缓存在 JVM 的堆空间中。在 Python 中,我们会始终序列化要持久化存储的数据,所以持久化级别默认值就是以序列化后的对象存储在 JVM 堆空间中。当我们把数据写到磁盘或者堆外存储上时,也总是使用序列化后的数据。
持久化级别:
检查点checkpoint:
RDD能够在第一次计算完之后,将计算结果保存到内存、本地文件系统或者HDFS中。通过缓存,Spark避免了RDD上的重复计算,能够极大地提高计算速度。但是如果缓存丢失了,则需重新计算。如果计算特别复杂或者计算特别耗时,那么缓存丢失对于整个job的影响是不容忽视的。为了避免缓存丢失重新计算带来的开销,Spark有引入了检查点(checkpoint)机制。
checkpoint将RDD持久化到磁盘,还可以切断RDD之间的依赖关系(这里的意思是,后面的RDD找数据的时候,就可以直接从checkpoint持久化目录里找了)。
checkpoint 的执行原理:
(1)当RDD的job执行完毕后,会从finalRDD从后往前回溯。
(2)当回溯到某一个RDD调用了checkpoint方法,会对当前的RDD做一个标记。
(3)Spark框架会自动启动一个新的job,重新计算这个RDD的数据,将数据持久化到HDFS上。
优化:对RDD执行checkpoint之前,最好对这个RDD先执行cache,这样新启动的job只需要将内存中的数据拷贝到HDFS上就可以,省去了重新计算这一步。
使用:
public class Demo04CheckPoint {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setMaster("local").setAppName("test");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> lines = sc.textFile("./word");
sc.setCheckpointDir("./checkpoint");
lines = lines.cache();
lines.checkpoint();
lines.collect();
sc.stop();
sc.close();
}
}
当然此处只是为了测试,一般我们会把checkpoint持久化的数据存储在HDFS上面。