Spark 运行时对哪些数据会做缓存?

在Spark应用执行过程中,某些需要多次使用或者重新计算的数据会进行缓存,为后续更多的计算操作复用,避免了再次计算,从而减少应用的执行时间,加速整体计算进度。

那么,在计算过程中,哪些数据是需要被缓存的。接下来我们通过一个简单的例子来回答一下。

如下示例,首先对输入数据进行map()计算,得到 mappedRDD,然后再对 mappedRDD 分别进行两种计算:reduceByKey + foreach(println) 和 groupByKey + foreach(println)。

java 复制代码
//输入数据
var inputRDD = sc.parallelize(Array[(Int,String)])(
(1,"a"),(2,"b"),(3,"c"),(4,"d"),(5,"e"),(3,"f"),(2,"g"),(1,"h"),(2,"i"),
),3)
val mappedRDD = inputRDD.map(r => (r._1 + 1, r._2))
// mappedRDD.cache()
val reducedRDD = mappedRDD.reduceByKey((x,y) => x + "_" + y, 2)
reducedRDD.foreach(println)
val groupedRDD = mappedRDD.groupByKey(3).mapValues(V => V.toList)
groupedRDD.foreach(println)

由于应用中存在两个foreach()行动算子,那么就会形成两个job,并且这两个job都是从inputRDD开始计算的。如下图所示。

不难看出,在这两个job中,inputRDD => mappedRDD 的计算流程都是一样的,理论上第二个job可以直接从 mappedRDD 开始进行计算的。

我们在代码中取掉mappedRDD.cache()语句的注释,声明一下mappedRDD需要被缓存。

需要注意的是:cache() 操作表示将数据直接写入到内存中,并且cache() 是lazy操作,并不会立即执行的,只有在第一个job运行时才将要缓存的数据写入内存中。

java 复制代码
//输入数据
var inputRDD = sc.parallelize(Array[(Int,String)])(
(1,"a"),(2,"b"),(3,"c"),(4,"d"),(5,"e"),(3,"f"),(2,"g"),(1,"h"),(2,"i"),
),3)
val mappedRDD = inputRDD.map(r => (r._1 + 1, r._2))
mappedRDD.cache()
val reducedRDD = mappedRDD.reduceByKey((x,y) => x + "_" + y, 2)
reducedRDD.foreach(println)
val groupedRDD = mappedRDD.groupByKey(3).mapValues(V => V.toList)
groupedRDD.foreach(println)

如下图所示,对 mappedRDD 进行缓存之后,可以避免第二个job再重复map() 计算。

但是带来的代价就是会只占用很多内存空间来缓存数据,试想,如果mappedRDD 包含了上亿个record,那么其存储将带来很大的内存消耗。这时设置缓存并非最优解,需要权衡计算成本和存储成本。

在本例中,map()操作的计算逻辑很简单,只需要少量的计算成本,如果mappedRDD需要很大的存储空间时,那么就不会对其进行缓存。

缓存机制实际上是一种用空间来换时间的方式,那么如何判断数据是否应该被设置缓存呢?

在 spark 缓存机制中,只要满足如下三个条即可:

(1)会被重复使用的数据 。并且被重复使用次数越多,那么缓存带来的性价比也会越高。一般来说,迭代型和交互型的应用会比较适合。

(2)数据不适合过大 。因为数据量级过大,会占用大量的存储空间,导致内存不足,会直接降低数据计算可使用的空间(可参考上一篇spark内存的介绍)。虽然说缓存过大也可以存放在磁盘中,但是磁盘IO的成本也比较高,甚至不如重新计算的成本低。

(3)非重复缓存的数据。什么意思呢?其实就是如果缓存了某RDD,那么与其存在一对一血缘的父RDD就不需要缓存了。在本例中,如果对 mappedRDD 进行了缓存,那么就没有必要对 inputRDD 进行缓存了。除非有新的job会重复用到inputRDD且不会用到 mappedRDD。

除此之外,不仅RDD 可以被缓存,广播数据task计算结果数据也可以被缓存。

相关推荐
田里的水稻4 分钟前
C++_队列编码实例,从末端添加对象,同时把头部的对象剔除掉,中的队列长度为设置长度NUM_OBJ
java·c++·算法
ponnylv14 分钟前
深入剖析Spring Boot启动流程
java·开发语言·spring boot·spring
前行的小黑炭30 分钟前
Android 协程的使用:结合一个环境噪音检查功能的例子来玩玩
android·java·kotlin
李少兄1 小时前
解决IntelliJ IDEA 提交代码时无复选框问题
java·ide·intellij-idea
cyforkk1 小时前
Spring Boot @RestController 注解详解
java·spring boot·后端
叫我阿柒啊2 小时前
从Java全栈到前端框架:一次真实面试的深度复盘
java·spring boot·typescript·vue·database·testing·microservices
点云SLAM2 小时前
C++ 常见面试题汇总
java·开发语言·c++·算法·面试·内存管理
sniper_fandc2 小时前
IDEA修改系统缓存路径,防止C盘爆满
java·ide·intellij-idea
aristo_boyunv2 小时前
拦截器和过滤器(理论+实操)
java·数据仓库·hadoop·servlet
半夏陌离2 小时前
SQL 入门指南:排序与分页查询(ORDER BY 多字段排序、LIMIT 分页实战)
java·前端·数据库