cache()和persist()用了反而更慢了——缓存什么时候该用什么时候不该用?
先说结论
缓存只在一个场景下有用:同一份DF被多次重复使用。如果一份DF只用一次,cache不但没有加速效果,还会浪费内存和序列化时间。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
很多人学Spark时听到“cache可以加速”,就到处加cache。结果作业不但没变快,反而更慢了,甚至OOM了。今天把“该不该缓存”和“怎么缓存”彻底讲清楚。
为什么缓存会变慢?
每次cache都有代价:
① 序列化开销:把数据从Tungsten的紧凑格式转换为Java对象存入内存,耗时耗内存
② GC压力增大:内存中存了大量缓存对象,GC频率和停顿时间都会增加
③ 挤占Execution内存:Shuffle和Sort可用的内存减少,可能导致溢出到磁盘
所以如果DF只用一次,缓存就是“花钱买了个不需要的东西”。
什么时候该缓存?
场景一:同一份DF被多个下游使用
// ✔ 该缓存:baseDF被两个下游使用
val baseDF = spark.read.parquet("hdfs://.../user_events")
.filter(col("dt") === "2025-03-15")
.cache() // 避免重复读Parquet和filter
// 下游一:统计DAU
val dau = baseDF.select("user_id").distinct().count()
// 下游二:统计事件分布
val eventDist = baseDF.groupBy("event_type").count()
场景二:迭代计算(机器学习等)
每次迭代都会重新计算整个DAG,cache让每次迭代从缓存开始而不是从源头重算。
cache() vs persist()的区别
| 方法 | 存储级别 | 适用场景 |
|---|---|---|
| cache() | MEMORY_AND_DISK(DataFrame默认) | 内存够用的场景,最常用 |
| persist(MEMORY_ONLY) | 纯内存,溢出则重算 | 小数据集,重算成本低 |
| persist(MEMORY_AND_DISK) | 内存不够溢出到磁盘 | 大数据集,推荐 |
| persist(DISK_ONLY) | 纯磁盘 | 数据量巨大且内存紧张 |
| persist(MEMORY_AND_DISK_SER) | 序列化后存内存+磁盘 | 省内存但多一次反序列化 |
DataFrame的cache()等价于persist(StorageLevel.MEMORY_AND_DISK),这和RDD的cache()(MEMORY_ONLY)不同!面试时能主动说出这个差异,非常加分。
实战踩坑:忘记unpersist导致内存泄漏
我在出海硬件公司遇到过一个问题:某个ETl作业跑着跑着越来越慢,最后OOM。排查后发现是在一个循环里每次迭代都cache了一个DataFrame,但没有unpersist。累积了几十个缓存把内存吃干净了。
// ✔ 正确做法:用完后unpersist释放内存
val cachedDF = baseDF.cache()
// ... 使用cachedDF ...
cachedDF.unpersist() // 释放缓存
总结
缓存只在“同一份DF被多次使用”时才有价值。cache()和persist()的区别在于存储级别。用完记得unpersist()。面试时能说清楚“什么时候该缓存、什么时候不该、缓存有什么代价”,就很有深度。
下一篇聊Spark SQL执行计划怎么看——Catalyst优化器到底做了什么?这是理解Spark性能的关键。
你们生产环境用cache多吗?评论区聊聊~
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长