目录
一、概述
二、RDD的核心概念
2.1 Partition
2.2 Partitioner
2.3 RDD的依赖关系
2.4 Stage
2.5 PreferredLocation
2.6 CheckPoint
三、RDD的持久化
3.1 概述
3.2 概念
3.3 RDD持久化级别
3.3.1 MEMORY_ONLY
3.3.2 MEMORY_AND_DISK
3.3.3 MEMORY_ONLY_SER
3.3.4 OMEMORY_AND_DISK_SER
3.3.5 DISK_ONLY
3.3.6 MEMORY_ONLY2和MEMORY_AND_DISK2
3.3.7 OFF_HEAP
3.4 RDD 持久化原则
3.5 删除RDD持久化缓存
一、概述
RDD是 Spark 中最基本的数据抽象,代表一个不可变、可分区、元素可并行计算的集合。RDD具有自动容错、位置感知性调度和可伸缩等特点。RDD 允许用户在执行多个查询时显式地将数据集缓存在内存中,后续查询能够重用该数据集,这极大地提升了查询效率。
二、RDD的核心概念
2.1 Partition
RDD内部的数据集在逻辑上和物理上都被划分为多个分区(Partition)以提高运行的效率,分区数量决定了计算的并行度,每一个分区内的数据都在一个单独的任务中被执行,如果在计算过程中没有指定分区数,那么 Spark 会采用默认分区数量。默认分区数量为程序运行分配到的CPU核数。
2.2 Partitioner
Partitioner 是 RDD的分区函数。分区函数不但决定了 RDD本身的分区数量,也决定了其父 RDD Shuffle 输出时的分区数量。Spark 实现了基于 Hash(HashPartitioner)和基于范围(RangePartitioner)的两种分区函数。
注意:只有对于Key-Value的RDD才会有 Partitioner,而非 Key-Value的RDD的Parititioner 值是None。
2.3 RDD的依赖关系
RDD的每次转换都会生成一个新的 RDD,因此RDD之间会有前后依赖关系。当在计算过程中出现异常情况导致部分分区数据丢失时,Spark 可以通过依赖关系从父 RDD 中重新计算丢失的分区数据,而不需要对 RDD上的所有分区全部重新计算。RDD的依赖分为窄依赖和宽依赖。
- 窄依赖:如果父 RDD的每个分区最多只能被子RDD的一个分区使用,则称之为窄依赖。
- 宽依赖:如果父 RDD的每个分区都可以被子RDD的多个分区使用,则称之为宽依赖。
窄依赖的每个子RDD的Partition 的生成操作都是可以并行的,而宽依赖则需要所有父Partition Shuffle结果完成后再被执行。Spark的窄依赖和宽依赖如图所示:
2.4 Stage
Stage 是由一组 RDD组成的可进行优化的执行计划。如果 RDD的依赖关系为窄依赖,则可放在同一个 Stage 中运行;若 RDD的依赖关系为宽依赖,则要划分到不同Stage 中。这样,当Spark 执行作业时,会按照Stage 划分不同的RDD,生成一个完整的最优的执行计划,使每个 Stage 内的 RDD都尽可能在各个节点上并行地被执行,
如图所示:
阶段三 包含 阶段一和 阶段二,其中,阶段一为宽依赖,阶段二为窄依赖。
2.5 PreferredLocation
PreferredLocation 是一个用于存储每个 Partition 的优先位置的列表。对于每个 HDFS文件来说,这个列表保存的是每个 Partition 所在的块的位置也就是该HDFS文件的“划分点”。
2.6 CheckPoint
CheckPoint 是Spark 提供的一种基于快照的缓存机制。当需要计算的 RDD 过多时,为了避免任务执行失败后重新计算之前的 RDD,可以对 RDD 做快照(CheckPoint)处理,检查 RDD 是否被计算,并将结果持久化到磁盘或 HDFS 上。
此外Spark 提供另一种缓存机制 Cache,Cache 缓存数据由 Executor 管理,当Executor 消失时Cache 缓存的数据将被清除,而 CheckPoint将数据保存到永久性磁盘或HDFS,当计算出现运行错误时,Job可以从CheckPoint 点继续计算。
三、RDD的持久化
3.1 概述
Spark 可以跨节点在内存中持久化 RDD。当持久化 RDD时,每个节点都会在内存中缓存计算后的分区数据,当其他操作需要使用该 RDD 时,可以直接重用该缓存数据,这使得之后的 RDD 计算速度更快(通常超过10倍)。缓存是选代计算和交式计算的关键。
3.2 概念
应用程序可以使用 persist0或 cache0标记要缓存的 RDD,当调用操作(Action)执行计算时,计算结果将被缓存在节点的内存中。Spark 缓存具有容错性,如果 RDD的某个分区丢失,则该RDD将被自动重新计算。
每个持久化 RDD 都可以使用不同存储级别进行存储,Spark 允许将数据集存储在磁盘上或内存中。Spark 将需要缓存的数据序列化为 Java 对象(序列化可以节省磁盘或内存空间),然后跨节点复制到其他节点上,以便其他节点重用该数据。Spark 中缓存持久化级别是通过StorageLevel来设置的。具体代码如下
lineLengths.persist(storageLevel.MEMORY_ONLY());
3.3 RDD持久化级别
3.3.1 MEMORY_ONLY
使用未经过序列化的 Java 对象在内存中存储 RDD。当内存不够时,将不会进行持久化:当下次需要该 RDD 时,再从源头处重新计算。该策略是默认的持久化策略,当使用 cache()时,使用的是该持久化策略。
3.3.2 MEMORY_AND_DISK
使用未经过序列化的 Java 对象存储 RDD,优先尝试将RDD保存在内存中。如果内存不够,则会将 RDD写人磁盘文件;当下次需要该 RDD时从持久化的磁盘文件中读取该 RDD 即可。
3.3.3 MEMORY_ONLY_SER
MEMORY_ONLY_SER 的含义与 MEMORY_ONLY 类似唯一区别是MEMORY_ONLY_SER 会将 RDD中的数据进行序列化。在序列化过程中,RDD的每个 Partition 都将会被序列化成一个字节数组,这种方式更加节省内存,从而避免持久化的RDD占用过多内存导致JVM频繁GC。
3.3.4 OMEMORY_AND_DISK_SER
MEMORY_AND_DISK_SER 的含义与 MEMORY_AND_DISK类似。唯一区别是MEMORY_AND_DISK_SER会将RDD中的数据进行序列化。在序列化过程中,RDD的每个 Partition 都会被序列化成一个字节数组。这种方式更
加节省内存,从而避免持久化的 RDD占用过多内存导致频繁GC。
3.3.5 DISK_ONLY
使用未序列化的Java对象将 RDD全部写人磁盘文件。
3.3.6 MEMORY_ONLY2和MEMORY_AND_DISK2
对于上述任意一种持久化策略如果加上后缀 2,代表的是将每个持久化的数据都复制一份副本,并将副本保存到其他节点上。这种基于副本的持久化机制主要用于容错。假如某个节点挂掉,节点的内存或磁盘中的持久化数据丢失了,那么后续对 RDD 计算时还可以使用该数据在其他节点上的副本。如果没有副本,则只能将这些数据从头重新计算一遍。
3.3.7 OFF_HEAP
OFF_HEAP与MEMORY_ONLY_SER类似,OFF_HEAP将数据存储在堆外内存中。该参数需要Spark 启用堆外内存。
3.4 RDD 持久化原则
Spark 提供了丰富的存储级别,旨在通过不同存储级别的设置实现内存和CPU的最佳使用,具体开发中该如何选择持久化方案呢?以下为 Spark 官方提供的缓存持久化的选择流程。
- 如果RDD在默认存储级别(MEMORYONLY)下运行良好,则建议使用MEMORY_ONLY。该级别是CPU效率最高的类型,基于CPU快速计算可以使 RDD上的操作尽可能快地运行。
- 如果系统显示内存使用过高,则尝试使用MEMORY_ONLY_SER,并选择更快速的序列化库,以加快序列化时间和节省对象的存储空间。
- 如果要快速恢复故障,则建议使用副本存储级别。其他存储级别需要通过重新计算丢失的数据来保障缓存的完整性,而副本存储级别可以在其缓存对应的副本节点上直接执行任务,不用等待重新计算丢失的分区数据
3.5 删除RDD持久化缓存
Spark 会自动监视每个节点上的缓存使用情况,并以 LRU方式删除旧的数据分区如果想手动删除RDD,则可通过RDD.unpersist()方法完成。
今天Spark RDD的相关内容就分享到这里,可以关注Spark专栏《Spark》,后续不定期分享相关技术文章。如果帮助到大家,欢迎大家点赞+关注+收藏,有疑问也欢迎大家评论留言!