如前所述,SparkRDD是惰性求值的,而有时我们希望能多次使用同一个RDD。如果简单地对RDD调用行动操作,Spark每次都会重算RDD以及它的所有依赖。这在迭代算法中消耗格外大,因为迭代算法常常会多次使用同一组数据。下例就是先对RDD作一次计数、再把该RDD输出的一个小例子。
SparkConf conf = new SparkConf().setMaster("local").setAppName("simpleDemo");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> lines = sc.textFile("README.md");
System.out.println(lines.count());
System.out.println(StringUtils.join(lines.collect(), ","));
为了避免多次计算同一个RDD,可以让Spark对数据进行持久化。当我们让Spark持久化存储一个RDD时,计算出RDD的节点会分别保存他们所求出的分区数据。如果一个有持久化数据的节点发生故障,Spark会在需要用到缓存的数据时重算丢失的数据分区。如果希望节点故障的情况不会拖累我们的执行速度,也可以把数据备份到多个节点上。
出于不同的目的,我们可以为RDD选择不同的持久化级别。Java中,默认情况下persist()会把数据以序列化的形式缓存在JVM的堆空间中。当我们把数据写到磁盘或者堆外存储上时,也总是使用序列化后的数据。
| 级别 | 使用的空间 | CPU时间 | 是否在内存中 | 是否在磁盘上 | 备注 |
| MEMORY_ONLY | 高 | 低 | 是 | 否 | |
| MEMORY_ONLY_SER | 低 | 高 | 是 | 否 | |
| MEMORY_AND_DISK | 高 | 中等 | 部分 | 部分 | 如果数据在内存中放不下,则溢写到磁盘上 |
| MEMORY_AND_DISK_SER | 低 | 高 | 部分 | 部分 | 如果数据在内存中放不下,则溢写到磁盘上,在内存中存放序列化后的数据 |
| DISK_ONLY | 低 | 高 | 否 | 是 |
下面来看下持久化在Java中的用法
SparkConf conf = new SparkConf().setMaster("local").setAppName("simpleDemo");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> lines = sc.textFile("README.md");
lines.persist(StorageLevels.MEMORY_AND_DISK_SER_2);
System.out.println(lines.count());
System.out.println(StringUtils.join(lines.collect(), ","));
我们在第一次对这个RDD调用行动操作前就调用了persist()方法。persist()调用本身不会触发强制求值。
如果要缓存的数据太多,内存中放不下,Spark会自动利用最近最少使用(LRU)的缓存策略把最老的分区从内存中移除。对于仅把数据存放在内存中的缓存级别,下一次要用到已经被移除的分区时,这些分区就需要重新计算。但是对于使用内存与磁盘的缓存级别的分区来说,被移除的分区都会写入磁盘。不论哪一种情况,都不必担心你的作业因为缓存了太多数据而被打断。不过,缓存不必要的数据会导致有用的数据被移出内存,带来更多重算的时间开销。
最后,RDD还有一个方法叫做unpersist(),调用该方法可以手动把持久化的RDD从缓存中移除。
本文深入探讨了Spark RDD的惰性求值特性及其在迭代算法中的效率问题,详细介绍了如何通过持久化机制避免重复计算,提高数据处理速度。文章列举了多种持久化级别,包括MEMORY_ONLY、MEMORY_ONLY_SER、MEMORY_AND_DISK、MEMORY_AND_DISK_SER、DISK_ONLY,并对比了它们在内存使用、CPU时间消耗、存储位置等方面的优缺点。
1019

被折叠的 条评论
为什么被折叠?



