spark的rdd是什么?
RDD(Resilient Distributed Dataset,弹性分布式数据集)是 Apache Spark 的核心概念。它是 Spark 最基本的数据抽象,代表一个不可变、可分区、里面的元素可并行计算的集合。
理解 RDD 可以拆解为以下三个关键词:
- Resilient(弹性):页意味着容错性。如果某个节点上的数据丢失或损坏,Spark 可以根据其“血统(Lineage)”重新计算生成该数据,而不需要像传统 MapReduce 那样进行昂贵的磁盘备份。
- Distributed(分布式):数据被分成多个分区(Partitions),分散存储在集群的不同节点上,从而可以进行并行计算。
- Dataset(数据集):它就像一个只读的记录集合,可以存放各种类型的数据(如 Java 对象、Python 对象、文本、键值对等)。
一、 RDD 的五大核心特性
在 Spark 内部,每一个 RDD 都可以用五个核心属性来表示:
- 分区列表(A list of partitions):一个 RDD 被分成多个分区,每个分区都在集群的一个节点上运行。分区是 Spark 并行计算的最小单位。
- 计算每个分裂的函数(A function for computing each split):Spark 会将计算逻辑(算子)发送给每个分区执行。
- 对其他 RDD 的依赖列表(A list of dependencies on other RDDs):RDD 之间存在依赖关系(称为血统 Lineage)。这是 RDD 能够容错的关键。
- 键值对 RDD 的分区器(An optional Partitioner for key-value RDDs):决定了数据如何分发到不同的分区中(例如:HashPartitioner、RangePartitioner)。
- 首选位置列表(An optional list of preferred locations):存储每个分区的最佳计算节点位置(实现“数据不动,代码动”的本地化计算,提高效率)。
二、 RDD 的核心操作(两类算子)
Spark 对 RDD 的操作分为两类:Transformation(转换算子) 和 Action(行动算子)。
1. Transformation(转换)
转换算子负责将一个 RDD 转换为另一个新的 RDD。
- 特点:惰性求值(Lazy Evaluation)。这意味着当你调用转换算子时,Spark 并不会立即执行计算,而只是记录下这个转换操作(构建 DAG 图)。
- 常用算子:
map(),filter(),flatMap(),reduceByKey(),groupByKey(),distinct()等。
2. Action(行动)
行动算子会触发真正的计算,并将结果返回给 Driver 程序或保存到外部存储系统(如 HDFS)。
- 特点:一旦调用 Action 算子,Spark 就会提交作业(Job),开始真正的分布式计算。
- 常用算子:
collect(),count(),first(),take(n),reduce(),saveAsTextFile()等。
三、 RDD 的容错机制:Lineage(血统)
这是 RDD “弹性”的来源。
- RDD 是不可变(Immutable)的。每次转换都会生成一个新的 RDD,从而形成一个链条,称为 Lineage(血统/依赖链)。
- 如果某个节点宕机,导致该节点上的 RDD 分区数据丢失,Spark 不需要从头开始重新计算,也不需要读取备份。它只需要根据 Lineage 找到这个分区是由父 RDD 的哪个分区转换来的,仅重新计算丢失的这部分数据。
依赖关系分为两类:
- 窄依赖(Narrow Dependency):父 RDD 的每个分区最多被一个子 RDD 分区使用(如
map,filter)。这种情况下,丢失数据只需局部重算,效率极高。 - 宽依赖(Wide Dependency,即 Shuffle):父 RDD 的一个分区会被多个子 RDD 分区使用(如
groupByKey,reduceByKey)。这种情况下会发生数据网络传输(Shuffle),容错恢复成本较高。
四、 代码示例(以 PySpark 为例)
以下是一个经典 Word Count(单词计数)的例子:
python
# 1. 创建 RDD (从外部文件读取)
text_rdd = sc.textFile("hdfs://path/to/file.txt")
# 2. Transformation 操作 (此时不进行实际计算)
words_rdd = text_rdd.flatMap(lambda line: line.split(" ")) # 切分单词
pairs_rdd = words_rdd.map(lambda word: (word, 1)) # 转换为 (word, 1) 键值对
word_counts_rdd = pairs_rdd.reduceByKey(lambda a, b: a + b) # 局部及全局聚合
# 3. Action 操作 (触发实际计算,并将结果保存)
word_counts_rdd.saveAsTextFile("hdfs://path/to/output")
五、 RDD 的局限性与现代 Spark 的演变
虽然 RDD 是 Spark 的基石,但在现代 Spark 开发中,直接使用 RDD 的场景已经变少。
RDD 的缺点:
- 缺乏结构化信息:RDD 只知道数据是一个个对象,但不知道对象内部有哪些字段、类型是什么。
- 没有自动优化:用户写什么代码,Spark 就怎么执行,无法像 SQL 那样进行自动查询优化。
演变:
因此,Spark 2.x/3.x 引入了更高层级的 API:DataFrame 和 Dataset。
- DataFrame/Dataset 在 RDD 的基础上加入了 Schema(表结构)。
- 它们底层使用 Catalyst 优化器 和 Tungsten 执行引擎,能够自动优化代码,性能远超直接编写 RDD。
总结:RDD 是 Spark 的底层灵魂。尽管现在推荐使用 DataFrame/Dataset 进行业务开发,但理解 RDD 的分区、容错、Lazy 机制和 Shuffle,对于深入理解 Spark 的底层原理和性能调优至关重要。
右滑查看面试常问