基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

spark rdd详解

RDD(Resilient Distributed Dataset,弹性分布式数据集)是 Apache Spark 的核心概念和最基础的数据抽象。理解 RDD 是掌握 Spark 编程和底层原理的关键。

以下是对 Spark RDD 的超详细解析,涵盖其定义、五大特性、算子分类、依赖关系、容错机制以及生命周期。


一、 什么是 RDD?

  • R - Resilient(弹性):表示数据可以自动容错。如果某个节点上的数据丢失或损坏,Spark 可以根据“血统(Lineage)”自动重建丢失的分区,无需人工干预。
  • D - Distributed(分布式):数据被切分成多个分区(Partitions),分布在集群的不同节点上,进行并行计算。
  • D - Dataset(数据集):它是一个只读的、装载着同类元素的集合。

通俗理解:RDD 就像一个“虚拟的数组”,你感觉它是一个整体,但实际上它被切成了很多碎片(分区),存放在不同的服务器上。你对这个“数组”进行的操作,会被 Spark 自动分发到各个服务器上并行执行。


二、 RDD 的五大核心属性(底层结构)

在 Spark 源码中,任何一个 RDD 都具备以下五个核心特征:

  1. A list of partitions(分区列表)
    • 一个 RDD 被分成多个分区,分区是 Spark 进行并行计算的最小单位。
  2. A function for computing each split(计算函数)
    • Spark 会将计算函数作用在每一个分区上(移动计算而非移动数据)。
  3. A list of dependencies on other RDDs(依赖关系列表)
    • 记录当前 RDD 是由哪些父 RDD 转换而来的(即 Lineage 血统),这是容错的基础。
  4. A Partitioner for Key-Value RDDs(分区器,可选)
    • 如果是键值对(Key-Value)类型的 RDD,会有一个分区器(如 HashPartitioner 或 RangePartitioner),决定数据如何分发到不同的分区。
  5. A list of preferred locations(优先位置列表,可选)
    • 记录每个分区期望存储的位置(比如 HDFS 块所在的本地节点)。Spark 在调度任务时,会尽量将计算任务分配到数据所在的物理节点上(实现数据本地化)。

三、 RDD 的两大算子(Operations)

RDD 的操作被称为“算子”,分为两类:转换算子(Transformation)行动算子(Action)

1. 转换算子(Transformation)

  • 特点惰性求值(Lazy Evaluation)。转换算子只记录 RDD 转换的轨迹(构建 DAG 图),并不立即触发真正的计算。
  • 返回值:返回一个新的 RDD。
  • 常见算子
    • map(func):对每个元素应用函数。
    • filter(func):过滤元素。
    • flatMap(func):扁平化映射(1对多)。
    • groupByKey():按 Key 分组。
    • reduceByKey(func):按 Key 分组并聚合(推荐,在 Map 端有 Combine 预聚合,效率高)。
    • join(otherRDD):连接两个 RDD。

2. 行动算子(Action)

  • 特点立即执行。一旦调用行动算子,Spark 就会提交作业(Job),开始真正的分布式计算。
  • 返回值:不再返回 RDD,而是返回具体的结果(如 Array, List, Long)或者将数据写入外部存储(如 HDFS)。
  • 常见算子
    • collect():将所有数据收集到 Driver 端(数据量大时易 OOM,需谨慎使用)。
    • count():返回元素个数。
    • first() / take(n):获取第一个或前 n 个元素。
    • reduce(func):对数据集进行聚合。
    • saveAsTextFile(path):保存到本地或 HDFS。

四、 RDD 的依赖关系与 Stage 划分

RDD 之间的依赖关系决定了 Spark 的调度模式和数据流向。依赖分为两种:

1. 窄依赖(Narrow Dependency)

  • 定义:每一个父 RDD 的分区最多被子 RDD 的一个分区使用(1对1 或 多对1)。
  • 特点:不需要进行 Shuffle(数据混洗),可以直接在内存中进行流水线(Pipeline)计算。
  • 典型算子map, filter, flatMap, union

2. 宽依赖(Wide Dependency / Shuffle Dependency)

  • 定义:同一个父 RDD 的分区会被多个子 RDD 的分区所使用(1对多 或 多对多)。
  • 特点必须进行 Shuffle。必须跨节点传输数据,会有大量的磁盘 IO 和网络传输,是性能瓶颈。
  • 典型算子groupByKey, reduceByKey, join(未分区的情况下)。
plaintext
窄依赖 (Narrow):
[Parent Partition 1]  ----->  [Child Partition 1]
[Parent Partition 2]  ----->  [Child Partition 2]

宽依赖 (Wide - Shuffle):
[Parent Partition 1]  ---\-->  [Child Partition 1]
                          X 
[Parent Partition 2]  ---/-->  [Child Partition 2]

3. Stage(阶段)的划分

Spark 提交 Job 后,DAG 调度器(DAGScheduler)会从后往前划分 Stage。

  • 划分依据遇到宽依赖(Shuffle)就切分出一个新的 Stage
  • 窄依赖会被合并到同一个 Stage 中,以 pipeline 的方式并行执行。
  • Stage 之间有先后顺序,前一个 Stage 执行完并把 Shuffle 数据写到磁盘后,后一个 Stage 才能开始。

五、 RDD 的容错与持久化机制

1. 容错机制:Lineage(血统)

RDD 内部保存了它是由哪些父 RDD 计算得来的。如果某个分区数据丢失,Spark 不需要从头读取所有数据,而是只需要根据 Lineage 重新计算该分区即可。

2. 持久化(缓存)

如果一个 RDD 被多次重复使用,每次使用都会重新计算,效率极低。可以通过持久化将 RDD 的计算结果保存起来。

  • cache():默认将 RDD 缓存在内存中(等价于 persist(StorageLevel.MEMORY_ONLY))。
  • persist(level):可以指定存储级别(如内存、磁盘、多副本等):
    • MEMORY_ONLY(仅内存)
    • MEMORY_AND_DISK(内存不够写磁盘)
    • MEMORY_ONLY_SER(序列化后存内存,省空间但费 CPU)
  • 注意:持久化是惰性的,只有在第一次触发 Action 算子时才会真正写入缓存。

3. 检查点(Checkpoint)

当 Lineage(血统)过长时,容错成本会很高。Checkpoint 可以将 RDD 数据写入可靠的外部存储(如 HDFS),并斩断(截断)该 RDD 之前的血统关系

  • 与 Cache 的区别
    • Cache 把数据缓存在内存/本地磁盘,Executor 挂了或者作业结束了,缓存就消失了。Lineage 依然保留。
    • Checkpoint 把数据写到 HDFS,物理上永久保存。Lineage 被切断。

六、 RDD 工作流程示例(WordCount)

经典的 WordCount 代码如何体现 RDD 的生命周期:

python
# 1. 创建 RDD (从外部源读取)
lines_rdd = sc.textFile("hdfs://...")

# 2. 转换算子 (窄依赖)
words_rdd = lines_rdd.flatMap(lambda line: line.split(" "))
# 3. 转换算子 (窄依赖)
pairs_rdd = words_rdd.map(lambda word: (word, 1))

# 4. 转换算子 (宽依赖 - 触发 Shuffle)
word_counts_rdd = pairs_rdd.reduceByKey(lambda a, b: a + b)

# 5. 行动算子 (触发 Job 执行,保存数据)
word_counts_rdd.saveAsTextFile("hdfs://output")

执行过程

  1. Spark 解析代码,构建出 DAG 图(lines -> words -> pairs -> word_counts)。
  2. 遇到 reduceByKey(宽依赖),将 Job 划分为两个 Stage:
    • Stage 1:读取 HDFS -> flatMap -> map -> 写出 Shuffle Map File。
    • Stage 2:读取 Shuffle Map File -> reduceByKey -> 保存到 HDFS。
  3. 任务分配到 Executor 上并行执行。

七、 RDD 的局限性与现代 Spark

尽管 RDD 非常强大,但在现代 Spark 开发中,我们更多地使用 DataFrameDataset

RDD 的局限性

  1. 缺乏结构信息:Spark 只知道 RDD 里是一个个 Java/Python 对象,不知道对象的内部结构,无法针对字段进行自动优化。
  2. 序列化开销大:在垃圾回收(GC)和网络传输时,Java 对象的序列化和反序列化开销极大。

进化

  • DataFrame = RDD + Schema(结构信息)。Spark 拥有了类似 SQL 的执行计划优化器(Catalyst),性能大幅提升。
  • Dataset = DataFrame 的强类型版本(支持 Scala/Java 编译期类型检查)。

总结

  • RDD 是基石:虽然 DataFrame/Dataset 更常用,但它们的底层依然是 RDD。
  • 核心掌握:理解分区、惰性求值、宽/窄依赖、Shuffle、持久化机制,是进行 Spark 调优(解决倾斜、内存溢出)的必备底层知识。
00:00
00:00