本文是大数据系列第 69 篇,深入解析 Spark 最核心的数据抽象——RDD 的五大特性与设计原理。

完整图文版(含截图):CSDN 原文 | 掘金

什么是 RDD

RDD(Resilient Distributed Dataset,弹性分布式数据集) 是 Spark 框架最核心的数据抽象,代表一个不可变、可分区、元素可并行计算的分布式集合

RDD 的”弹性”体现在:

  • 存储弹性:数据可在内存与磁盘之间自动切换
  • 容错弹性:通过血缘关系(Lineage)自动重建丢失的分区
  • 计算弹性:Task 失败时自动重试
  • 分片弹性:可根据需要重新分区

所有 Spark 高级 API(DataFrame、Dataset)底层均构建在 RDD 之上,理解 RDD 是掌握 Spark 原理的基础。

五大关键特征

1. 分区列表(Partitions)

RDD 由多个分区(Partition)组成,每个分区包含数据集的一个子集,是 Spark 并行处理的基本单位。分区数量决定了并行度:

  • 从 HDFS 读取时,默认每个 Block(128MB)对应一个分区
  • 可通过 sc.textFile(path, numPartitions)rdd.repartition(n) 手动调整

2. 计算函数(Compute Function)

每个 RDD 都携带一个计算函数,描述如何从父 RDD 计算出当前 RDD 的数据。例如:

val lines = sc.textFile("hdfs:///data/input")
val words = lines.flatMap(_.split(" "))  // 计算函数:按空格切分
val counts = words.map(w => (w, 1))      // 计算函数:构造 (word, 1) 键值对

计算函数采用惰性求值——调用 flatMapmap 时不立即执行,只有触发 collect()count() 等 Action 操作时才真正运算。

3. 依赖关系(Dependencies)

RDD 记录自身与父 RDD 之间的依赖关系,分为两种:

窄依赖(Narrow Dependency):父 RDD 的每个分区最多被子 RDD 的一个分区依赖,无需 Shuffle。典型算子:mapfilterunion

父分区 1 → 子分区 1
父分区 2 → 子分区 2   (一对一,可流水线执行)

宽依赖(Wide Dependency / Shuffle Dependency):父 RDD 的一个分区被子 RDD 的多个分区依赖,需要 Shuffle 跨节点传输数据。典型算子:groupByKeyreduceByKeyjoin

父分区 1 → 子分区 1, 子分区 2
父分区 2 → 子分区 1, 子分区 2  (多对多,需 Shuffle)

宽依赖是 Stage 划分的边界——Spark 在每次 Shuffle 处切割出一个新 Stage。

4. 分区器(Partitioner)

键值对 RDD,可指定分区器控制数据在节点间的分布:

  • HashPartitioner(默认):按 key.hashCode % numPartitions 分区,适合大多数场景
  • RangePartitioner:按 key 的范围分区,适合需要有序输出的场景(如 sortByKey

正确选择分区器可以减少不必要的 Shuffle,是性能调优的重要手段。

5. 优先位置列表(Preferred Locations)

基于数据本地性原则,RDD 记录每个分区的优先计算位置。Spark 调度器会尽量将 Task 分配到数据所在节点,避免网络传输:

  • PROCESS_LOCAL:数据在同一 JVM 进程(最优)
  • NODE_LOCAL:数据在同一节点的磁盘或另一个进程
  • RACK_LOCAL:数据在同一机架的其他节点
  • ANY:数据在其他机架(最差,跨机架传输)

惰性求值与 DAG 优化

Spark 的惰性求值使其能够在执行前对整个计算图(DAG)进行优化:

  1. 用户调用一系列 Transformation,Spark 只记录逻辑计划
  2. 触发 Action 时,Spark 分析完整 DAG
  3. 将 DAG 按 Shuffle 边界划分为多个 Stage
  4. 同一 Stage 内的算子合并(Pipeline)执行,减少中间数据落盘

容错机制:血缘关系重建

RDD 不需要通过数据复制实现容错,而是通过**血缘关系(Lineage)**记录完整的转换链路。当某个分区数据丢失时,Spark 只需从最近的检查点(或源数据)重新执行相关转换,而无需重算整个 Job。

textFile → flatMap → map → reduceByKey → collect
    ↑                                        |
    └──────── 分区丢失时,从源重建 ───────────┘

典型应用场景

场景适合用 RDD 的原因
迭代式机器学习(K-Means、PageRank)多次复用同一数据集,cache() 缓存内存
复杂自定义数据处理逻辑底层 API 提供完全控制权
非结构化数据处理支持任意 Scala/Java 对象
多阶段 ETL 流水线惰性求值 + DAG 优化减少中间落盘
键值对聚合统计reduceByKeyaggregateByKey 高效 Shuffle

掌握 RDD 的五大特性是深入理解 Spark 执行原理、进行性能调优的前提,也是学习 DataFrame/Dataset 高级 API 的基础。