考察点
这题看似简单,但能说清「血缘是否切断」这个本质区别的人不多。面试官想听:两者在存储位置、血缘处理、失败恢复行为上的对比;checkpoint 为什么是 action 而 cache 是 lazy 的;迭代计算里为什么要两者配合使用;以及 StorageLevel 的选择逻辑。追问常往「cache 会不会丢数据」「checkpoint 目录管理」「persist 的副本级别」方向走。
参考答案
一句话对比
cache/persist 是性能优化:把计算结果留在内存或磁盘,下次用不用再算,但血缘还在,数据丢了按血缘重算。checkpoint 是容错保障:把结果写到可靠外部存储(HDFS),切断血缘,从此这个 RDD 相当于数据源,前面的链路全扔掉。
逐维度对比
| 维度 | cache / persist | checkpoint |
|---|---|---|
| 目的 | 复用提速 | 长链容错 |
| 存储 | executor 内存/本地磁盘 | HDFS 等可靠存储 |
| 血缘 | 保留 | 切断 |
| 丢失后 | 按血缘重算(可跳缓存祖先) | 从 checkpoint 文件读回 |
| 执行时机 | lazy,action 触发 | eager,本身是 action |
| 可靠性 | 单副本默认,executor 挂即丢 | 依赖 HDFS 副本,高可靠 |
cache/persist 的细节
cache() 就是 persist(MEMORY_ONLY) 的简写。StorageLevel 的选择是内存和可靠性的权衡:
MEMORY_ONLY:默认。内存放得下就快,放不下的分区下次用时重算。MEMORY_AND_DISK:最实用的级别。内存放不下的部分 spill 到 executor 本地磁盘,重算降级为读盘,性能损失可控。MEMORY_ONLY_SER/MEMORY_AND_DISK_SER:序列化存储,省内存(Java 对象头开销大,序列化后体积可能小一半以上)费 CPU(每次用要反序列化)。大缓存、内存紧张时用。*_2副本级别:两份副本,executor 挂了一个还有另一个。生产上很少用,因为重算通常比维护副本便宜——副本级别是血淋淋的教训换来的经验,不是常规配置。
cache 是 lazy 的,标记后第一个 action 才真正物化。用完要 unpersist() 释放,否则 storage 内存被占着。
checkpoint 的细节
checkpoint 解决的是血缘过长的问题。迭代计算(机器学习梯度下降、图算法 PageRank)里 RDD 链滚到几十上百代,driver 端血缘元数据膨胀,任一 partition 丢失都要回溯重算全链,代价不可接受。checkpoint 把当前 RDD 物化到 HDFS,把它的血缘替换成一个指向 checkpoint 文件的引用,从此重算只回溯到这里。
两个关键用法细节:
sc.setCheckpointDir("hdfs:///checkpoints/xxx") // 必须先设目录
rdd.cache() // 先 cache!
rdd.checkpoint() // 再 checkpoint
rdd.count() // 触发:第一次算走 cache,checkpoint 直接读缓存写盘
不 cache 直接 checkpoint,这个 RDD 会被算两遍——第一遍是 checkpoint 自己的物化,第二遍是后续 action 用的时候。cache 之后 checkpoint 直接从内存写盘,只算一遍。这是 checkpoint 最容易踩的坑。
checkpoint 是 action(立即执行),且目录需要用户管理——job 结束后 checkpoint 文件不会自动清理,长期跑的任务要规划目录生命周期。
什么时候用哪个
判断逻辑很简单:
- 一个 RDD/DataFrame 会被多个下游分支用到(比如一张中间结果表 join 多个维度),cache。
- 血缘太长(迭代、复杂 ETL 多步链),担心重算代价或 driver 元数据膨胀,在关键节点 checkpoint。
- 两者都要:先 cache 再 checkpoint,既提速又保容错。
反面教材也常见:什么都 cache,结果 storage 内存被挤爆,execution 区域 spill 到飞起,整体反而变慢。cache 是有成本的,只缓存确实复用且重算代价高的数据。
一个容易混淆的点
cache 之后如果 executor 挂了,缓存的分区丢失,Spark 会按血缘重算——cache 不是容错机制,只是性能优化。所以「cache 了就不会重算」是错的,「cache 了就算 executor 挂也能恢复」也是错的(会重算,只是能命中缓存的部分跳过)。真正的容错只有两条路:血缘重算、checkpoint 读回。
可能的追问
- checkpoint 和 cache 后重启 job,数据还在吗? checkpoint 在 HDFS,job 重启后可以从 checkpoint 文件恢复(新 job 里要重新建立指向 checkpoint 的 RDD);cache 在 executor 内存/本地盘,job 结束全部释放,不存在跨 job 复用。
- DataFrame 也有 checkpoint 吗? 有,
df.checkpoint()(Spark 2.1+,需要 setCheckpointDir)。语义相同:切断血缘、物化到可靠存储。但 DataFrame 的 cache 比 RDD 更常见,因为 Catalyst 优化后重算代价相对低。 - persist 之后 unpersist 的时机怎么把握? 最后一个使用这个 RDD 的 action 之后立刻 unpersist。Streaming 场景里是每批次处理完 unpersist 上一批次的缓存。漏了 unpersist 是生产环境内存泄漏(storage 区域膨胀)的常见原因。