本文是大数据系列第 69 篇,深入解析 Spark 最核心的数据抽象——RDD 的五大特性与设计原理。
什么是 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) 键值对
计算函数采用惰性求值——调用 flatMap、map 时不立即执行,只有触发 collect()、count() 等 Action 操作时才真正运算。
3. 依赖关系(Dependencies)
RDD 记录自身与父 RDD 之间的依赖关系,分为两种:
窄依赖(Narrow Dependency):父 RDD 的每个分区最多被子 RDD 的一个分区依赖,无需 Shuffle。典型算子:map、filter、union。
父分区 1 → 子分区 1
父分区 2 → 子分区 2 (一对一,可流水线执行)
宽依赖(Wide Dependency / Shuffle Dependency):父 RDD 的一个分区被子 RDD 的多个分区依赖,需要 Shuffle 跨节点传输数据。典型算子:groupByKey、reduceByKey、join。
父分区 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)进行优化:
- 用户调用一系列 Transformation,Spark 只记录逻辑计划
- 触发 Action 时,Spark 分析完整 DAG
- 将 DAG 按 Shuffle 边界划分为多个 Stage
- 同一 Stage 内的算子合并(Pipeline)执行,减少中间数据落盘
容错机制:血缘关系重建
RDD 不需要通过数据复制实现容错,而是通过**血缘关系(Lineage)**记录完整的转换链路。当某个分区数据丢失时,Spark 只需从最近的检查点(或源数据)重新执行相关转换,而无需重算整个 Job。
textFile → flatMap → map → reduceByKey → collect
↑ |
└──────── 分区丢失时,从源重建 ───────────┘
典型应用场景
| 场景 | 适合用 RDD 的原因 |
|---|---|
| 迭代式机器学习(K-Means、PageRank) | 多次复用同一数据集,cache() 缓存内存 |
| 复杂自定义数据处理逻辑 | 底层 API 提供完全控制权 |
| 非结构化数据处理 | 支持任意 Scala/Java 对象 |
| 多阶段 ETL 流水线 | 惰性求值 + DAG 优化减少中间落盘 |
| 键值对聚合统计 | reduceByKey、aggregateByKey 高效 Shuffle |
掌握 RDD 的五大特性是深入理解 Spark 执行原理、进行性能调优的前提,也是学习 DataFrame/Dataset 高级 API 的基础。