转载

Spark RDD Persistence

Spark最为重要的特性之一就是可以在多个操作(Action)之间,将一个或多个RDD关联的数据集(Dataset)以分区(Partition)为单位进行持久化(Persist)或缓存(Cache),存储介质通常是内存(Memory)。

被持久化或缓存的RDD A可以在两种情况下被很好地“重复”利用:

(1)直接依赖:操作(Action)直接应用于RDD A之上;

(2)间接依赖:操作(Action)间接应用于RDD B之上,而RDD B来源于RDD A;

持久化或缓存是迭代式计算和交互式应用的关键技术,通常可以提升10位以上的计算速度。

实际应用中,RDD的持久化或缓存选项是通过persist()或cache()发出的,之后如果某个操作(Action)触发该RDD的数据第一次被计算,那么计算的结果数据(也就是该RDD的数据)就会以分区的形式被缓存于计算节点的内存中;而且这些数据是可以实现容错的,如果这个RDD的某些分区数据丢失(因为节点故障),这些分区的数据可以在使用时通过世代信息(Lineage)被自动恢复。

RDD的存储形式或存储介质是可以通过存储级别(Storage Level)被定义的。例如,将数据持久化到磁盘、将Java对象序列化之后(有利于节省空间)缓存至内存、开启复制(RDD的分区数据可以被备份到多个节点防止丢失)或者使用堆外内存(Tachyon)。persist()可以接收一个StorageLevel对象(Scala、Java、Python)用以定义存储级别,如果使用的是默认的存储级别(StorageLevel.MEMORY_ONLY),Spark提供了一个便利方法:cache()。

存储级别选项如下:

MEMORY_ONLY 默认选项,RDD的(分区)数据直接以Java对象的形式存储于JVM的内存中,如果内存空间不足,某些分区的数据将不会被缓存,需要在使用的时候根据世代信息重新计算。
MYMORY_AND_DISK RDD的数据直接以Java对象的形式存储于JVM的内存中,如果内存空间不中,某些分区的数据会被存储至磁盘,使用的时候从磁盘读取。
MEMORY_ONLY_SER RDD的数据(Java对象)序列化之后存储于JVM的内存中(一个分区的数据为内存中的一个字节数组),相比于MEMORY_ONLY能够有效节约内存空间(特别是使用一个快速序列化工具的情况下),但读取数据时需要更多的CPU开销;如果内存空间不足,处理方式与MEMORY_ONLY相同。
MEMORY_AND_DISK_SER 相比于MEMORY_ONLY_SER,在内存空间不足的情况下,将序列化之后的数据存储于磁盘。
DISK_ONLY 仅仅使用磁盘存储RDD的数据(未经序列化)。

MEMORY_ONLY_2,

MEMORY_AND_DISK_2, etc.

以MEMORY_ONLY_2为例,MEMORY_ONLY_2相比于MEMORY_ONLY存储数据的方式是相同的,不同的是会将数据备份到集群中两个不同的节点,其余情况类似。
OFF_HEAP(experimental) RDD的数据序例化之后存储至Tachyon。相比于MEMORY_ONLY_SER,OFF_HEAP能够减少垃圾回收开销、使得Spark Executor更“小”更“轻”的同时可以共享内存;而且数据存储于Tachyon中,Spark集群节点故障并不会造成数据丢失,因此这种方式在“大”内存或多并发应用的场景下是很有吸引力的。需要注意的是,Tachyon并不直接包含于Spark的体系之内,需要选择合适的版本进行部署;它的数据是以“块”为单位进行管理的,这些块可以根据一定的算法被丢弃,且不会被重建。

注意:使用PySpark(即使用Python开发Spark应用程序)时,所有需要存储的数据都会使用Pickle进行序列化,这种行为与存储级别无关。

Spark推荐用户将需要重复使用的RDD通过persist()或cache()显式持久化。同时我们需要知道,会触发“Shuffle”的操作是特殊的,例如reduceByKey,即使没有用户的显式persist,它也会自动持久化“Shuffle”的中间结果,以防止“Shuffle”过程中某些节点故障导致整个输入数据被重新计算。

那么我们应该如何选取持久化的存储级别呢?实际上存储级别的选取就是Memory与CPU之间的双重权衡,可以参考下述内容:

(1)如果RDD的数据可以很好的兼容默认存储级别(MEMORY_ONLY),那么优先使用它,这是CPU工作最为高效的一种方式,可以很好地提高运行速度;

(2)如果(1)不能满足,则尝试使用MEMORY_ONLY_SER,且选择一种快速的序列化工具,也可以达到一种不错的效果;

(3)一般情况下不要把数据持久化到磁盘,除非计算是非常“昂贵”的或者计算过程会过滤掉大量数据,因为重新计算一个分区数据的速度可能要高于从磁盘读取一个分区数据的速度;

(4)如果需要快速的失败恢复机制,则使用备份的存储级别,如MEMORY_ONLY_2、MEMORY_AND_DISK_2;虽然所有的存储级别都可以通过重新计算丢失的数据实现容错,但是备份机制使得大部分情况下应用无需中断,即数据丢失情况下,直接使用备份数据,而不需要重新计算数据的过程;

(5)如果处于大内存或多应用的场景下,OFF_HEAP可以带来以下的好处:

a. 它允许Spark Executors可以共享Tachyon的内存数据;

b. 它很大程序上减少JVM垃圾回收带来的性能开销;

c. Spark Executors故障不会导致数据丢失。

最后,Spark可以自己监测“缓存”空间的使用,并使用LRU算法移除旧的分区数据。我们也可以通过显式调用RDD unpersist()手动移除数据。

正文到此结束
Loading...