Spark是基于内存的计算模型,但是当compute chain非常长或者某个计算代价非常大时,能将某些计算的结果进行缓存就显得很方便了。Spark提供了两种缓存的方法 Cache 和 checkPoint。本章只关注 Cache (基于spark-core_2.10),在后续的章节中会提到 checkPoint.

主要从以下三方面来看

  1. persist时发生什么
  2. 执行action时如何去缓存及读取缓存
  3. 如何释放缓存

定义缓存

spark的计算是lazy的,只有在执行action时才真正去计算每个RDD的数据。要使RDD缓存,必须在执行某个action之前定义RDD.persist(),此时也就定义了缓存,但是没有真正去做缓存。RDD.persist会调用到SparkContext.persistRDD(rdd),同时将RDD注册到ContextCleaner中(后面会讲到这个ContextCleaner)。

def persist(newLevel: StorageLevel): this.type = {
// TODO: Handle changes of StorageLevel
if (storageLevel != StorageLevel.NONE && newLevel != storageLevel) {
throw new UnsupportedOperationException(
"Cannot change storage level of an RDD after it was already assigned a level")
}
sc.persistRDD(this)
// Register the RDD with the ContextCleaner for automatic GC-based cleanup
sc.cleaner.foreach(_.registerRDDForCleanup(this))
storageLevel = newLevel
this
}

sc.persistRDD很简单,将(rdd.id, rdd)加到persistentRdds中。persistentRDDs一个HashMap,key就是rdd.id,value是一个包含时间戳的对rdd的弱引用。persistentRDDs用来跟踪已经被标记为persist的RDD的引用的。

所以在定义缓存阶段,做了两件事:一是设置了rdd的StorageLevel,而是将rdd加到了persistentRdds中并在ContextCleaner中注册。

缓存

当执行到某个action时,真正计算才开始,这时会调用DAGScheduler.submitJob去提交job,通过rdd.iterator()来计算partition。

final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
if (storageLevel != StorageLevel.NONE) {
SparkEnv.get.cacheManager.getOrCompute(this, split, context, storageLevel)
} else {
computeOrReadCheckpoint(split, context)
}
}

iterator的逻辑很清楚,如果srorageLevel被标记过了就去CacheManager取,否则自己compute或者从checkPoint读取。

在cacheManager.getOrCompute中,通过RDDBlockId尝试去BlockManager中得到缓存的数据。如果缓存得不到(第一次计算),并调用computeOrReadCheckPoint去计算,并将结果cache起来,cache是通过putInBlockManger实现。根据StorageLevel,如果是缓存在内存中,会将结果存在MemoryStore的一个HashMap中,如果是在disk,结果通过DiskStore.put方法存到磁盘的某个文件夹中。这个文件及最终由Utils中的方法确定

private def getOrCreateLocalRootDirsImpl(conf: SparkConf): Array[String] = {
if (isRunningInYarnContainer(conf)) {
// If we are in yarn mode, systems can have different disk layouts so we must set it
// to what Yarn on this system said was available. Note this assumes that Yarn has
// created the directories already, and that they are secured so that only the
// user has access to them.
getYarnLocalDirs(conf).split(",")
} else if (conf.getenv("SPARK_EXECUTOR_DIRS") != null) {
conf.getenv("SPARK_EXECUTOR_DIRS").split(File.pathSeparator)
} else {
// In non-Yarn mode (or for the driver in yarn-client mode), we cannot trust the user
// configuration to point to a secure directory. So create a subdirectory with restricted
// permissions under each listed directory.
Option(conf.getenv("SPARK_LOCAL_DIRS"))
.getOrElse(conf.get("spark.local.dir", System.getProperty("java.io.tmpdir")))
.split(",")
.flatMap { root =>
try {
val rootDir = new File(root)
if (rootDir.exists || rootDir.mkdirs()) {
val dir = createTempDir(root)
chmod700(dir)
Some(dir.getAbsolutePath)
} else {
logError(s"Failed to create dir in $root. Ignoring this directory.")
None
}
} catch {
case e: IOException =>
logError(s"Failed to create local root dir in $root. Ignoring this directory.")
None
}
}
.toArray
}
}

如果已经缓存了,那么cacheManager.getOrCompute在调用blockManger.get(RDDBlockId)时会返回结果。get会先调用getLocal在本地获取,如果本地没有则调用getRemote去远程寻找,getRemote会call BlockMangerMaster.getLocation得到缓存的地址。

释放

Spark通过调用rdd.unpersit来释放缓存,这是通过SparkContext.unpersistRDD来实现的。在unpersistRDD中,rdd会从persistentRdds中移除,并通知BlockManagerMaster去删除数据缓存。BlockManagerMaster会通过消息机制告诉exectutor去删除内存或者disk上的缓存数据。

那么问题来了,如果用户不通过手动来unpersit,那缓存岂不是越积越多,最后爆掉吗?

是的,你的想法完全合理。因此Spark会自动删除不在scope内的缓存。“不在scope”指的是在用户程序中已经没有了该RDD的引用,RDD的数据是不可读取的。这里就要用到之前提到的ContextCleaner。ContextCleaner存了CleanupTaskWeakReference弱引用及存放该引用的队列。当系统发生GC将没有强引用的rdd对象回收后,这个弱引用会加入到队列中。ContextCleaner起了单独的一个线程轮询该队列,将队列中的弱引用取出,根据引用中的rddId触发sc.unpersistRDD。通过这样Spark能及时的将已经垃圾回收的RDD对应的cache进行释放。这里要清楚rdd与数据集的关系,rdd只是一个定义了计算逻辑的对象,对象本身不会包含其所代表的数据,数据要通过rdd.compute计算得到。所以系统回收rdd,只是回收了rdd对象,并没有回收rdd代表的数据集。

此外,SparkContext中还有一个MetadataCleaner,该cleaner会移除persistentRdds中的过期的rdd。(笔者一直没清楚这个移除和cache释放有什么关系??)

Reference:

https://spark.apache.org/docs/latest/programming-guide.html#rdd-persistence

http://jerryshao.me/architecture/2013/10/08/spark-storage-module-analysis/

https://github.com/JerryLead/SparkInternals/blob/master/markdown/english/6-CacheAndCheckpoint.md

http://blog.csdn.net/yueqian_zhu/article/details/48177353

http://www.cnblogs.com/jiaan-geng/p/5189177.html

Spark 代码走读之 Cache的更多相关文章

  1. UNIMRCP 代码走读

    基于UNIMRCP1.5.0的代码走读 与 填坑记录 1. server启动配置加载 入口:unimrcp_server.c static apt_bool_t unimrcp_server_load ...

  2. Spark代码调优(一)

    环境极其恶劣情况下: import org.apache.spark.SparkContext import org.apache.spark.rdd.RDD import org.apache.sp ...

  3. Spark代码中设置appName在client模式和cluster模式中不一样问题

    问题 Spark应用名在使用yarn-cluster模式提交时不生效,在使用yarn-client模式提交时生效,如图1所示,第一个应用是使用yarn-client模式提交的,正确显示我们代码里设置的 ...

  4. Spark代码Eclipse远程调试

    我们在编写Spark Application或者是阅读源码的时候,我们很想知道代码的运行情况,比如参数设置的是否正确等等.用Logging方式来调试是一个可以选择的方式,但是,logging方式调试代 ...

  5. Github提交Spark代码

    记录下提交过程,易忘供查询用.内容源自田总的分享. 1)在github上fork一份最新的master代码 2)用社区代码库创建本地仓库 git clone https://github.com/ap ...

  6. WebRTC代码走读(八):代码目录结构

    转载注明出处http://blog.csdn.net/wanghorse ├── ./base //基础平台库,包括线程.锁.socket等 ├── ./build //编译脚本,gyp ├── ./ ...

  7. Qt Creator插件工作流程代码走读

    Qt Creator有个很风骚的插件管理器PluginManager,还有个很骚包的插件说明PluginSpec.基本上,所有的Qt程序的入口都是传统的C程序一样,代码流程从main()函数开始.  ...

  8. 本地开发spark代码上传spark集群服务并运行

    打包 :右击.export.Java .jar File 把TestSpark.jar包上传到spark集群服务器的 spark_home下的myApp下: 提交spark任务: cd /usr/lo ...

  9. lda spark 代码官方文档

    http://spark.apache.org/docs/1.6.1/mllib-clustering.html#latent-dirichlet-allocation-lda http://spar ...

随机推荐

  1. 【UOJ139】【UER #4】被删除的黑白树

    题意: 很久很久以前,有一棵树加入了 UOJ 群. 这天,在它讨论“一棵树应该怎么旋转”的时候一不小心被删除了,变成了被删除的树. 突然间,它突然发现它失去了颜色,变成了一棵纯白的树.这让它感觉很焦躁 ...

  2. Django 连接MySQL 通过pymysql 库

    ython3 如何安装pymysql 库,在此不再做多的讲解,如果有想知道如何安装的朋友,请求参考下面的连接地址: 第一步:应用setting.py 文件设置mysql 数据库连接相关属性.   DA ...

  3. nginx与tomcat搭建集群,负载均衡

    --------------------------------------------------- 搭建环境(在桌面上即可完成测试) 先准备2个tomcat服务器 解压tomcat压缩包 得到 把 ...

  4. [Node.js] Proxy Requests for Local and Remote Service Parity

    High availability apps require that no distinction be made between local and remote services. Attach ...

  5. 通达OA 小飞鱼工作流在线培训教程(一)HTML基础介绍

    应一些刚接触工作流设计朋友的要求,这里开设一个系列教程,对通达OA工作流设计相关的内容做个介绍.方便解决一些日常经常出现的问题,希望对刚刚接触这部分工作的朋友能够有些帮助. 工作流设计须要多方面的知识 ...

  6. Ant批量处理jmeter脚本

    Ant是一个可以把代码从某个地方拿来,编译,再拷贝到某个地方去的构建工具.一时冲动学习一下,顺便王婆卖瓜尝试着处理jmeter的脚本,于是,采坑之旅也从此开始.本文省略ant安装步骤和ant脚本说明, ...

  7. Spring In Action读书笔记

    第一章 1.Spring採用4种策略减少Java开发复杂度 基于POJO的轻量级和最小侵入性编程 依赖注入和面向接口实现松耦合 基于切面和惯例进行声明式编程 通过切面和模板降低样板式代码 PS:POJ ...

  8. 通过setSystemUiVisibility实现状态栏跟Activity之间的位置关系

    曾经说到去除状态栏和标题栏总会用到动态代码的方式实现: getWindow().setFlags(WindowManager.LayoutParams. FLAG_FULLSCREEN , Windo ...

  9. 英语发音规则---F字母

    英语发音规则---F字母 一.总结 一句话总结: 1.F/FF发[f]音? fly [flaɪ] vi. 飞 fine [faɪn] adj. 好的 float [fləʊt] vt. 使漂浮 fra ...

  10. python黏包解决方案

    解决方案 # 我们可以借助一个模块,这个模块可以把要发送的数据长度转换成固定长度的字节.这样客户端每次接 # 收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据 ...