转载请注明出处:http://www.cnblogs.com/BYRans/
算法
RDD的存放和管理都是由Spark的存储管理模块实现和管理的。本文从架构和功能两个角度对Spark的存储管理模块进行介绍。缓存
从架构角度,存储管理模块主要分为如下两层:网络
在存储管理模块的通讯层,每一个Executor上的BlockManager只负责管理其自身Executor所拥有的数据块原信息,而不会管理其余Executor上的数据块元信息;而Driver端的BlockManager拥有全部已注册的BlockManager信息和数据块元信息,所以Executor的BlockManager每每是经过向Driver发送信息来得到所须要的非本地数据的。架构
RDD是由不一样的分区组成的,咱们所进行的转换和执行操做都是在每一块独立的分区上各自进行的。而在存储管理模块内部,RDD又被视为由不一样的数据块组成,对于RDD的存取是以数据块为单位的,本质上分区(partition)和数据块(block)是等价的,只是看待的角度不一样。同时,在Spark存储管理模块中存取数据的最小单位是数据块,全部的操做都是以数据块为单位的。框架
前面章节已经提到:存储管理模块以数据块为单位进行数据管理,数据块是存储管理模块中最小的操做单位。在存储管理模块中管理着各类不一样的数据块,这些数据块为Spark框架提供了不一样的功能,Spark存储管理模块中所管理的几种主要数据块为:socket
从功能角度,存储管理模块能够分为如下两个主要部分:函数
存储管理模块能够分为两大块,一是RDD的缓存,二是Shuffle数据的持久化。接下来将介绍存储管理模块如何从内存和磁盘两个方面对RDD进行缓存。性能
对于RDD的各类操做,如转换操做、执行操做,咱们将操做函数施行于RDD之上,而最终这些操做都将施行于每个分区之上,所以能够这么说,在RDD上的全部运算都是基于分区的。而在存储管理模块内,咱们所接触到的每每是数据块这个概念,在存储管理模块中对于数据的存取都是以数据块为单位进行的。分区是一个逻辑上的概念,而数据块是物理上的数据实体,咱们操做的分区和数据块,它们二者之间有什么关系呢?本小节咱们将介绍分区与数据块的关系。spa
在Spark中,分区和数据块是一一对应的,一个RDD中的一个分区对应着存储管理模块中的一个数据块,存储管理模块接触不到也并不关心RDD,它只关心数据块,对于数据块和分区之间的映射则是经过名称上的约定进行的。.net
这种名称上的约定是按以下方式创建的:Spark为每个RDD在其内部维护了独立的ID号,同时,对于RDD的每个分区也有一个独立的索引号,所以只要知道ID号和索引号咱们就能找到RDD中的相应分区,也就是说“ID号+索引号”就能全局惟一地肯定这个分区。这样以“ID号+索引号”做为块的名称就天然地创建起了分区和块的映射。
在显示调用调用函数缓存咱们所需的RDD时,Spark在其内部就创建了RDD分区和数据块之间的映射,而当咱们须要读取缓存的RDD时,根据上面所提到的映射关系,就能从存储管理模块中取得分区对应的数据块。下图展现了RDD分区与数据块之间的映射关系。
当以默认或者基于内存的持久化方式缓存RDD时,RDD中的每一分区所对应的数据块是会被存储管理模块中的内存缓存(Memory Store)所管理的。内存缓存在其内部维护了一个以数据块名为键,块内容为值的哈希表。
在内存缓存中有一个重要的问题是,当内存不是或是已经到达所设置的阈值时应如何处理。在Spark中对于内存缓存可以使用的内存阈值有这样一个配置:spark.storage.memoryFraction。默认状况下是0.6,也就是说JVM内存的60%可被内存缓存用来存储块内容。当咱们存储的数据块所占用的内存大于60%时,Spark会采起一些策略释放内存缓存空间:丢弃一些数据块,或是将一些数据块存储到磁盘上以释放内存缓存空间。是丢弃仍是存储到磁盘上,依赖于进行操做的这些数据块的持久化选项,若持久化选项中包含了磁盘缓存,则会将这些块已入磁盘进行缓存,反之则直接删除。
那么直接删除是否会影响Spark程序的错误恢复机制呢?这取决于依赖关系的可回溯性,若该RDD所依赖的祖先RDD是可被回溯并可用的,那么该RDD所对应的块被删除是不会影响错误恢复的。反之,若该RDD已是祖先RDD,且数据已没法被回溯到,那么程序就会出错。lost executor错误是否是就是这个缘由?
从上面的介绍能够看出,内存缓存对于数据块的管理是很是简单的,本质上就是一个哈希表加上一些存取策略。
磁盘缓存管理数据块的方式为,首先,这些数据块会被存放到磁盘中的特定目录下。当咱们配置spark.local.dir时,咱们就配置了存储管理模块磁盘缓存存放数据的目录。磁盘缓存初始化时会在这些目录下建立Spark磁盘缓存文件夹,文件夹的命名方式是:spark-local-yyyyMMddHHmmss-xxxx,其中xxxx是一随机数。伺候全部的块内容都将存储到这些建立的目录中。
其次,在磁盘缓存中,一个数据块对应着文件系统中的一个文件,文件名和块名称的映射关系是经过哈希算法计算所得的。
总而言之,数据块对应的文件路径为:dirId/subDirId/block_id。这样咱们就创建了块和文件之间的对应关系,而存取块内容就变成了写入和读取相应的文件了。
被缓存的数据块是可容错恢复的,若RDD的某一分区丢失,他会经过继承关系自动从新得到。
对于RDD的持久化,Spark为咱们提供了不一样的选项,使咱们能将RDD持久化到内存、磁盘,或是以序列化的方式持久化到内存中,设置能够在集群的不一样节点之间存储多份拷贝。全部这些不一样的存储策略都是经过不一样的持久化选项来决定的。
存储管理模块能够分为两大块,一是RDD的缓存,二是Shuffle数据的持久化。介绍完RDD缓存,接下来介绍Shuffle数据持久化。
下图为Spark中Shuffle操做的流程示意图
首先,每个Map任务会根据Reduce任务的数据量建立出相应的桶,桶的数量是M*R,其中M是Map任务的个数,R是Reduce任务的个数。
其次,Map任务产生的结果会根据所设置的分区算法填充到每一个桶中。这里的分区算法是可自定义的,固然默认的算法是根据键哈希到不一样的桶中。
当Reduce任务启动时,它会根据本身任务的ID和所依赖的Map任务的ID从远端或本地的存储管理模块中取得相应的桶做为任务的输入进行处理。
Shuffle数据与RDD持久化的不一样之处在于:
默认的方式会产生大量的文件,如1000个Map任务和1000个Reduce任务,会产生1000000个Shuffle文件,这会对磁盘和文件系统的性能形成极大的影响,所以有了第二种是实现方式,将分时运行的Map任务所产生的Shuffle数据块合并到同一个文件中,以减小Shuffle文件的总数。对于第二种存储方式,示意图以下:
前面介绍了Shuffle数据块的存取,下面咱们来介绍Shuffle数据块的读取和传输。Shuffle是将一组任务的输出结果从新组合做为下一组任务的输入这样的一个过程,因为任务分布在不一样的节点上,所以为了将重组结果做为输入,必然涉及Shuffle数据的读取和传输。
在Spark存储管理模块中,Shuffle数据的读取和传输有两种方式:
前者是默认的获取方式,经过配置spark.shuffle.use.netty为true,能够启用第二种获取方式。之因此有两种Shuffle数据的获取方式,是由于默认的方式在一些状况下没法充分利用网络带宽,用户能够经过比较两种方式在性能上的差别来自行决定选用哪一种Shuffle数据获取方式。
总的来讲,Spark存储管理模块为Shuffle数据的持久化作了许多有别于RDD持久化的工做,包括存取Shuffle数据块的方式,以及读取和传输Shuffle数据块的方式,全部这些实现都是为了使Shuffle得到更好的性能和容错。