本节书摘来自华章出版社《spark大数据分析:核心概念、技术及实践》一书中的第3章,第3.7节,作者[美] 穆罕默德·古勒(mohammed guller),更多章节内容可以访问云栖社区“华章计算机”公众号查看。
3.7 缓存
除了将数据驻留在内存中以外,缓存在rdd中也扮演了另外一个重要的角色。就像之前所说的,创建rdd有两种方式,从存储系统中读取数据或者应用其他现存rdd的转换操作。默认情况下,当一个rdd的操作方法被调用时,spark会根据它的父rdd来创建这个rdd,这有可能导致父rdd的创建。如此往复,这个过程一直持续到spark找到根rdd,而后spark通过从过存储系统读取数据的方式创建根rdd。操作方法被调用一次,上面说的过程就会执行一遍。每次调用操作方法,spark都会遍历这个调用者rdd的血统树,执行所有的转换操作来创建它。
考虑下面的例子。

尽管上面的代码只调用了一次textfile方法,但是日志文件会被从硬盘中读取两次。这是因为调用了两次操作方法count。在调用errorlogs.count时,日志文件第一次被读取,调用warninglogs.count时,日志文件被再次读取。这只是个简单的例子,现实世界中的应用会有更多的各种转换和操作。
如果一个rdd缓存了,spark会执行到目前为止的所有转换操作并为这个rdd创建一个检查点。具体来说,这只会在第一次在一个缓存的rdd上调用某操作的时候发生。类似于转换方法,缓存方法也是惰性的。
如果一个应用缓存了rdd,spark并不是立即执行计算并把它存储在内存中。spark只有在第一次在缓存的rdd上调用某操作的时候才会将rdd物化在内存中。而且这第一次操作并不会从中受益,后续的操作才会从缓存中受益。因为它们不需要再执行从存储系统中读取数据开始的一系列操作。它们通常都运行得快多了。还有,那些只使用一次数据的应用使用缓存也不会有任何好处。只有那些需要对同样数据做多次迭代的应用才能从缓存中受益。
如果一个应用把rdd缓存在内存中,spark实际上是把它存储在每个worker节点上执行者的内存中了。每个执行者把它所计算的rdd分区缓存在内存中。
3.7.1 rdd的缓存方法
rdd类提供了两种缓存方法:cache和persist。
cache
cache方法把rdd存储在集群中执行者的内存中。它实际上是将rdd物化在内存中。
下面的例子展示了怎么利用缓存优化上面的例子。
persist
persist是一个通用版的cache方法。它把rdd存储在内存中或者硬盘上或者二者皆有。它的输入参数是存储等级,这是一个可选参数。如果调用persist方法而没有提供参数,那么它的行为类似于cache方法。
persist方法支持下列常见的存储选项。
memory_only:当一个应用把 memory_only作为参数调用persist方法时,spark会将rdd分区采用反序列化java对象的方式存储在worker节点的内存中。如果一个rdd分区无法完全载入worker节点的内存中,那么它将在需要时才计算。
disk_only:如果把disk_only作为参数调用persist方法,spark会物化rdd分区,把它们存储在每一个worker节点的本地文件系统中。这个参数可以用于缓存中间的rdd,这样接下来的一系列操作就没必要从根rdd开始计算了。
memory_and_disk:这种情况下,spark会尽可能地把rdd分区存储在内存中,如果有剩余,就把剩余的分区存储在硬盘上。
memory_only_ser:这种情况下,spark会采用序列化java对象的方式将rdd分区存储在内存中。一个序列化的java对象会消耗更少的内存,但是读取是cpu密集型的操作。这个参数是在内存消耗和cpu使用之间做的一个妥协。
memory_and_disk_ser:spark会尽可能地以序列化java对象的方式将rdd分区存储在内存中。如果有剩余,则剩余的分区会存储在硬盘上。
3.7.2 rdd缓存是可容错的
在分布式环境中可容错性是相当重要的。之前我们就已经知道了当节点出故障的时候spark是怎么自动把计算作业转移到其他节点的。spark的rdd机制同样也是可容错的。
即使一个缓存rdd的节点出故障了,spark应用也不会崩溃。spark会在另外节点上自动重新创建、缓存出故障的节点中存储的分区。spark利用rdd的血统信息来重新计算丢失的缓存分区。
3.7.3 缓存内存管理
spark采用lru算法来自动管理缓存占用的内存。只有在必要时,spark才会从缓存占用的内存中移除老的rdd分区。而且,rdd还提供了名为unpersist的方法。应用可以调用这个方法来从缓存占用的内存中手动移除rdd分区。