关于Spark Cache

347 阅读3分钟

关于Spark Cache

最近有一个用户抱怨很简单的spark作业运行了很长时间,检查发现用户把所有的中间dataset都cache了一遍,在建议用户去掉不必要的cache后,打算对spark的作业执行过程中的cache进行一个深入调研,进而进一步优化我们的服务。脑海中初步有一个思路如下图所示,

image.png

  1. 对Union节点进行cache, 因为union节点在执行的时候被Agg节点和Join节点分别引用两次
  2. Agg节点和Join节点执行之后,对Union节点进行uncache.

Cache, Persist和Checkpoint

在开始做调研和实验之前,先简单介绍下Spark的cache, persist和hcheckpoint的区别与联系:

  1. persist, RDD持久化的,将RDD以及RDD的血统(记录了这个RDD如何产生)缓存到内存中,当缓存的RDD失效的时候(如内存损坏),它们可以通过血统重新计算来进行恢复。persist有多个存储级别,存储级别举例:
  • Memory_Only:只存于内存
  • Disk_Only:只存于磁盘
  • Memory_and_disk:内存不够,存于磁盘
  • Memory_only_ser:将数据以序列化的方式存于内存
  1. cache是persist的一种,值得注意的是dataset和RDD的cache是有区别的 spark中DataFrame或Dataset里的cache()方法默认存储等级为MEMORY_AND_DISK,这跟RDD.cache()的存储等级MEMORY_ONLY是不一样的
  2. 而checkpoint主要强调将数据做物理存储的(本地磁盘或HDFS上), 这三者都是做RDD持久化的,cache()和persist()是将数据默认缓存在内存中,checkpoint()是将数据做物理存储的(本地磁盘或Hdfs上),checkpoint 将 RDD 缓存到了 HDFS 中,同时忽略了它的血统(也就是RDD之前的那些依赖)。为什么要丢掉依赖?因为可以利用 HDFS 多副本特性保证容错,因此checkpoint后的RDD可以被其它应用程序读到,而persist的缓存则会在作业中断后消失。

什么时候应该CACHE?

市面上关于什么时候该用cache的文章很少,即使Spark官网是这样描述的

Spark also supports pulling data sets into a cluster-wide in-memory cache. This is very useful when data is accessed repeatedly, such as when querying a small “hot” dataset or when running an iterative algorithm like PageRank

其中提到了两个关键词“small”和"hot",话不多说,开始实验验证: 实验1: cache所有共享的(被引用两次及两次以上)dataset 实验2: 只cache数据比较小且共享的dataset 实验3: 跨action共享 实验4: 不cache

每组实验做十次,分别在简单的作业和复杂的作业上执行,实验现象: 实验1:

image.png

实验结果,

  1. 对于简单的ETL作业,执行时间:实验3 < 实验1 ~ 实验2 < 实验4
  2. 对于负责的作业,实验3 < 实验2 < 实验4< 实验1

原因分析:大量cache会占用spark内存,导致存储内存不够,缓存加到disk中,查阅executor日志验证:

Added rdd_xx_x on disk on ...

添加缓存本身的时间甚至可能超过重新计算的时间。

什么时候应该清除CACHE?

实验1: cache数据比较小且共享的dataset,不做为何unpersist 实验2: 孩子节点执行后unpersist

结果:实验1优于实验2 分析2: 实验2的思路本身有问题,由于spark cache和unpersist都是懒加载,都是在actio操作的时候执行,cache+unpersist相当于没有做cache. 查阅官网文档后发现,当内存不足时,Spark会基于LRU算法,自动清理cache,因此结论是不需要我们手动清除CACHE.

Spark automatically monitors cache usage on each node and drops out old data partitions in a least-recently-used (LRU) fashion. If you would like to manually remove an RDD instead of waiting for it to fall out of the cache, use the `RDD.unpersist()` method. Note that this method does not block by default. To block until resources are freed, specify `blocking=truewhen calling this method.