spark持久化操作 persist(),cache()

本文探讨Spark中数据持久化的重要性,通过对比缓存与非缓存情况下的计数操作时间,展示persist()与cache()如何有效减少重复计算,提高效率。深入解析StorageLevel配置选项,帮助读者理解不同持久化策略的适用场景。

spark对同一个RDD执行多次算法的默认原理:每次对一个RDD执行一个算子操作时,都会重新从源头处计算一遍。如果某一部分的数据在程序中需要反复使用,这样会增加时间的消耗。

为了改善这个问题,spark提供了一个数据持久化的操作,我们可通过persist()或cache()将需要反复使用的数据加载在内存或硬盘当中,以备后用。

以下是一个基于pyspark计数的测试

#-*- encoding='utf-8' -*-
from pyspark.sql import SparkSession
import time
from pyspark import StorageLevel
spark = SparkSession \
        .builder \
        .appName('test') \
        .getOrCreate()

data = spark.read.csv("hdfs:///user/sparknode/input/test_repartition/*",header=True).cache()

t1 = time.time()
print(data.count())
t2 = time.time()
print(t2-t1)

print(data.count())
t3 = time.time()
print(t3-t2)

print(data.count())
t4 = time.time()
print(t4-t3)


#该输出结果为
29999990
108.410
29999990
6.730
29999990
1.391



#如果没加持久化操作cache(),结果为
29999990
37.553783655166626
29999990
38.87501883506775
29999990
38.92804193496704

从结果可以看出,

未加cache()时,每一次计数操作所用的时间都一样,是否可说明,spark在每一个action结束后都将数据释放了?然后,下次action需要再重新读取。

加cache()后,第二第三次计数所用的时间明显下降,但第一次计数所用的时间却比正常的多,这说明cache()操作本身需要消耗一定的时间,怎么权衡就看实际情况了。

 

persist()与cache()操作

我们可在读取数据或transform之后连续的调用perisist()或cache()进行数据持久化。

data = spark.read.csv("hdfs:///user/sparknode/input/test_repartition/*",header=True) \
.persist(storageLevel=StorageLevel(False,True,False,False,1))

持久化操作可以将数据加载在内存或硬盘中,也可以备份多份,减少数据丢失后重新计算的时间。

采用persist()时,需要先载入StorageLevel方法,该方法主要用于持久化策略的选择

from pyspark import StroageLevel


#参数
StorageLevel(useDisk, useMemory, useOffHeap, deserialized, replication=1)

useDisk     是否使用硬盘
useMemory   是否使用内存
replication 备份份数

#_ser是采用了序列化存储方式,从而减小持久化带来的内存消耗

DISK_ONLY = StorageLevel(True, False, False, False, 1)

DISK_ONLY_2 = StorageLevel(True, False, False, False, 2)

MEMORY_AND_DISK = StorageLevel(True, True, False, False, 1)

MEMORY_AND_DISK_2 = StorageLevel(True, True, False, False, 2)

MEMORY_AND_DISK_SER = StorageLevel(True, True, False, False, 1)

MEMORY_AND_DISK_SER_2 = StorageLevel(True, True, False, False, 2)

MEMORY_ONLY = StorageLevel(False, True, False, False, 1)

MEMORY_ONLY_2 = StorageLevel(False, True, False, False, 2)

MEMORY_ONLY_SER = StorageLevel(False, True, False, False, 1)

MEMORY_ONLY_SER_2 = StorageLevel(False, True, False, False, 2)

OFF_HEAP = StorageLevel(True, True, True, False, 1)

而cache()实际上是调用了persist()方法,并选择了持久化策略为 storageLevel=StorageLevel(True, True, False, False, 1)

即 .cache()与.persist(storageLevel=StorageLevel(True, True, False, False, 1))效果是一样的,都是MEMORY_AND_DISK持久化策略。

<think> 我们正在讨论Spark持久化的存储级别(StorageLevel)。根据提供的引用材料,特别是引用[3]中提到的内容,我们可以总结出Spark中常见的持久化级别。 引用[1]提到:cache方法等价于StorageLevel.MEMORY_ONLY的persist方法。 引用[3]提到:MEMORY_AND_DISK_2中的"_2"表示数据的复制因子(replication factor),并且还提到了persist()cache()的区别。 因此,我们首先列出Spark中内置的存储级别,然后解释每个级别的含义。 Spark持久化级别定义在`StorageLevel`类中,常见的包括: 1. MEMORY_ONLY:将RDD作为反序列化的Java对象存储在JVM中。如果内存不足,某些分区将不会被缓存,并在需要时重新计算。 2. MEMORY_AND_DISK:将RDD作为反序列化的Java对象存储在JVM中。如果内存不足,将溢出的分区存储到磁盘上,并在需要时从磁盘读取。 3. MEMORY_ONLY_SER:将RDD序列化为Java对象(每个分区一个字节数组)存储在内存中。这比反序列化对象更节省空间,但读取时更消耗CPU。 4. MEMORY_AND_DISK_SER:类似于MEMORY_ONLY_SER,但溢出的分区会存储到磁盘。 5. DISK_ONLY:只将RDD分区存储在磁盘上。 6. 带后缀_2的级别:例如MEMORY_ONLY_2,MEMORY_AND_DISK_2等,表示每个分区在集群中两个节点上复制(即复制因子为2)。 此外,还有两个不常用的级别: 7. OFF_HEAP:将数据存储在堆外内存(如Tachyon)。这种模式可以避免垃圾回收开销,但读取速度较慢。 8. MEMORY_AND_DISK_SER_2:序列化存储,内存不足时存磁盘,且复制两份。 注意:cache()persist()的一个特例,它使用默认的存储级别(即MEMORY_ONLY)。 我们可以用代码展示如何设置存储级别: ```scala import org.apache.spark.storage.StorageLevel val rdd = sc.textFile("data.txt") rdd.persist(StorageLevel.MEMORY_AND_DISK) // 设置存储级别为MEMORY_AND_DISK ``` 或者使用cache(): ```scala rdd.cache() // 等同于 rdd.persist(StorageLevel.MEMORY_ONLY) ``` 当不再需要RDD的缓存时,可以调用`unpersist()`来释放资源: ```scala rdd.unpersist() ``` 根据引用[2],SparkContext中维护了一个哈希表persistRdds来记录所有被持久化的RDD,当调用unpersist时,会从该表中移除并删除存储的块。 总结一下,Spark持久化的存储级别主要从以下几个维度进行权衡: - 存储位置:内存、磁盘、堆外 - 存储形式:序列化(节省空间但增加CPU开销)或非序列化(占用空间大但访问快) - 复制因子:是否在多个节点上复制数据以提高容错性 在选择存储级别时,需要根据应用场景进行权衡。例如,如果内存充足,使用MEMORY_ONLY可以获得最佳性能;如果内存有限,可以考虑使用带序列化的级别;如果数据重要且希望避免重新计算,可以使用带复制的级别。 下面我们用表格形式总结: | 存储级别 | 描述 | 适用场景 | |----------|------|----------| | MEMORY_ONLY | 非序列化对象存储在内存,内存不足时重新计算 | 默认级别,内存充足时性能最佳 | | MEMORY_AND_DISK | 非序列化对象存储在内存,内存不足时溢写到磁盘 | 内存有限,但需要快速访问时 | | MEMORY_ONLY_SER | 序列化对象存储在内存(节省空间) | 内存有限,且可以接受CPU开销时 | | MEMORY_AND_DISK_SER | 序列化对象存储在内存,内存不足时溢写到磁盘 | 内存有限且数据较大,需要权衡CPU和I/O | | DISK_ONLY | 只存储在磁盘 | 数据量大,且对访问速度要求不高 | | 带_2的级别 | 在多个节点复制数据 | 需要容错且避免重新计算,或需要提升数据本地性 | | OFF_HEAP | 存储在堆外内存 | 需要避免垃圾回收,且数据可以接受较慢的访问速度 | 最后,我们生成相关问题。 注意:在回答中引用材料的地方需要添加引用标识,例如引用[1]提到cache方法等价于MEMORY_ONLY的persist方法,所以我们在描述cache时添加引用[1]。 </think> ### Spark持久化存储级别详解 Spark提供了多种持久化级别(StorageLevel),用于在内存和磁盘之间平衡性能、容错性和资源利用率。以下是核心存储级别及其特点: --- #### 一、基础存储级别 | **存储级别** | **内存使用** | **磁盘使用** | **序列化** | **复制因子** | **适用场景** | |-----------------------|--------------------------|--------------|-----------|-------------|----------------------------------------| | `MEMORY_ONLY` | 反序列化对象 | ❌ | ❌ | 1 | 默认级别,内存充足时性能最佳[^1] | | `MEMORY_AND_DISK` | 反序列化对象 | 内存溢出时 | ❌ | 1 | 内存不足时自动溢写到磁盘 | | `MEMORY_ONLY_SER` | **序列化**字节数组 | ❌ | ✔️ | 1 | 节省内存空间(减少GC压力)[^3] | | `MEMORY_AND_DISK_SER` | **序列化**字节数组 | 内存溢出时 | ✔️ | 1 | 大数据集且内存有限时最优 | | `DISK_ONLY` | ❌ | 直接存储 | ❌ | 1 | 超大数据集且内存严重受限 | > **序列化优势**: > 序列化存储可减少内存占用(节省50-80%空间),但增加CPU开销 > 公式:$\text{内存节省} \propto \frac{1}{\text{对象引用大小}}$ --- #### 二、带复制因子的高可用级别 | **存储级别** | **特点** | **典型场景** | |---------------------------|-------------------------------------------|----------------------------------| | `MEMORY_ONLY_2` | 内存存储双副本 | 计算节点易故障时 | | `MEMORY_AND_DISK_2` | 内存+磁盘双副本 | 高可用关键数据[^3] | | `MEMORY_ONLY_SER_2` | 序列化内存双副本 | 节省内存且需容错 | | `MEMORY_AND_DISK_SER_2` | 序列化内存+磁盘双副本 | 高可用大数据集 | > **复制因子作用**: > - 防止单点故障导致数据丢失 > - 提升数据本地性加速计算 > 复制因子为$n$时,存储开销增加$n$倍 --- #### 三、特殊存储级别 1. **OFF_HEAP** - 使用堆外内存(如Tachyon) - 避免JVM GC停顿 - 公式:$\text{GC暂停时间} \propto \text{堆大小}^2$ 2. **NONE** - 显式取消持久化 - 调用`unpersist()`释放资源[^2] --- #### 四、选型决策指南 ```mermaid graph TD A[数据特征] --> B{内存是否充足?} B -->|是| C{是否需容错?} C -->|是| D[选MEMORY_ONLY_2] C -->|否| E[选MEMORY_ONLY] B -->|否| F{是否CPU充裕?} F -->|是| G[选MEMORY_ONLY_SER] F -->|否| H{是否需容错?} H -->|是| I[选MEMORY_AND_DISK_SER_2] H -->|否| J[选MEMORY_AND_DISK_SER] ``` > **生产经验**: > - 迭代算法优先用`MEMORY_ONLY_SER` > - 流处理检查点用`MEMORY_AND_DISK_SER_2` > - 临时数据集及时`unpersist()`[^2] --- ### 使用示例 ```scala import org.apache.spark.storage.StorageLevel val rdd = sc.textFile("hdfs://data.log") // 带序列化的磁盘溢出存储 rdd.persist(StorageLevel.MEMORY_AND_DISK_SER) // 等价于cache()但使用序列化 rdd.persist(StorageLevel.MEMORY_ONLY_SER) // 释放持久化数据 rdd.unpersist() ``` --- ### 相关问题 1. 如何监控Spark持久化内存的使用情况? 2. 序列化存储为什么能减少GC压力? 3. 什么场景下应该使用OFF_HEAP存储? 4. 如何配置Spark的自动持久化策略? 5. 持久化数据在Spark DAG中如何影响任务调度? [^1]: 除了不同的存储级别,Spark 还通过在洗牌操作(如 reduceByKey )中持久化中间数据来优化性能,即使没有用户显式调用 persist [^2]: cache方法等价于StorageLevel.MEMORY_ONLY的persist方法,而persist方法也仅仅是简单修改了当前RDD的存储级别而已 [^3]: MEMORY_AND_DISK_2中的 "_2" 表示数据的复制因子(replication factor)。这意味着数据会被复制并存储在两个不同的
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值