在 Apache Spark 中,缓存(Cache)是一种优化手段,用于将中间计算结果存储在内存(或磁盘)中,以便后续重复使用,从而避免重复计算,提高作业的执行效率。缓存操作是惰性的,只有在触发行动算子时才会真正执行。
Spark 提供了多种缓存方法,其中最常用的是 cache() 和 persist()。
1. cache()
cache() 是 Spark 中最简单的缓存方法,它会将 RDD 数据存储到内存中,使用默认的存储级别(MEMORY_ONLY)。
• 存储级别:默认将数据存储在内存中,以反序列化的 Java 对象形式存储。
• 使用场景:适用于数据量较小且需要多次使用的场景。
• 示例代码:
scala
复制
val sc = new SparkContext("local", "CacheExample")
val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data, 2) // 将数据并行化为 RDD
distData
.cache() // 缓存 RDD
// 触发行动算子,计算并缓存结果
val sum = distData.reduce(_ + _)
println
(s"Sum: $sum")
// 再次使用 RDD 时,可以直接从缓存中读取,无需重新计算
val count = distData.count()
println
(s"Count: $count")
2. persist(level)
persist() 是更灵活的缓存方法,允许用户指定存储级别。Spark 提供了多种存储级别,可以根据实际需求选择最适合的存储方式。
• 存储级别:
◦ MEMORY_ONLY:将 RDD 以反序列化的 Java 对象形式存储在内存中(默认级别)。
◦ MEMORY_AND_DISK:将 RDD 存储在内存中,如果内存不足,则将多余的数据存储到磁盘中。
◦ MEMORY_ONLY_SER:将 RDD 以序列化的 Java 对象形式存储在内存中(节省内存空间,但访问速度稍慢)。
◦ MEMORY_AND_DISK_SER:将 RDD 以序列化的 Java 对象形式存储在内存中,如果内存不足,则存储到磁盘中。
◦ DISK_ONLY:仅将 RDD 存储在磁盘中。
◦ MEMORY_ONLY_2、MEMORY_AND_DISK_2 等:与上述级别类似,但会在两个节点上进行备份,提高容错性。
• 使用场景:适用于需要根据存储资源和性能需求灵活选择存储方式的场景。
• 示例代码:
scala
复制
val sc = new SparkContext("local", "PersistExample")
val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data, 2) // 将数据并行化为 RDD
// 使用 MEMORY_AND_DISK 级别缓存 RDD
distData
.persist(StorageLevel.MEMORY_AND_DISK)
// 触发行动算子,计算并缓存结果
val sum = distData.reduce(_ + _)
println
(s"Sum: $sum")
// 再次使用 RDD 时,可以直接从缓存中读取
val count = distData.count()
println
(s"Count: $count")
3. 缓存的注意事项
• 内存管理:Spark 的缓存机制是基于 LRU(最近最少使用)策略的。如果内存不足,Spark 会自动释放最久未使用的缓存数据。用户可以通过 unpersist() 方法手动释放缓存。
◦ 示例代码:
scala
复制
distData.unpersist() // 手动释放缓存
• 序列化与反序列化:使用序列化存储级别(如 MEMORY_ONLY_SER)可以节省内存,但访问速度会稍慢,因为需要进行序列化和反序列化操作。
• 缓存的触发时机:缓存操作是惰性的,只有在触发行动算子时才会真正执行。因此,如果只调用了 cache() 或 persist(),但没有触发行动算子,数据不会真正被缓存。
• 缓存的容错性:Spark 的缓存机制是容错的。如果某个节点的缓存数据丢失,Spark 会重新计算丢失的数据块,而不会影响整个作业的执行。